============================================================================== HEDERA PROMPTS — NATIVE HEDERA (HTS / HCS / HSCS) Generated 2026-08-01T02:33:10.182Z · https://hederaprompts.lovable.app/llms ============================================================================== Every megaprompt below is self-contained: it carries the Hedera testnet network facts, the ~900k gas rule for relay transfers and contract writes, the HTS alias-vs-long-zero address rule, mirror-node verification with retries, and revert decoding. Paste one prompt as a single message to your coding agent. ============================================================================== THEME · Dance & Choreography choreographers, dancers, dance teachers, movement directors ============================================================================== ------------------------------------------------------------------------------ IDEA dance-choreo-ledger-0 Title: Choreo Ledger Theme: Dance & Choreography (dance) · movement attribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely prove original choreography ownership and timestamp creations on a public ledger. Why Hedera: Hedera testnet smart contracts provide immutable proof of choreography authorship, accessible globally and transparently. Market: TAM $5B — global dance studio software market | SAM $500M — digital dance content IP management | SOM $50M — early adopters in dance choreography tech ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely prove original choreography ownership and timestamp creations on a public ledger. Discipline: Dance & Choreography (movement attribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide immutable proof of choreography authorship, accessible globally and transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-nft-tickets-1 Title: Dance NFT Tickets Theme: Dance & Choreography (dance) · event access control Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Sell and verify exclusive dance event tickets as tradable NFTs on-chain to prevent fraud. Why Hedera: Smart contracts automate issuance and verification of NFT tickets without intermediaries. Market: TAM $5B — global dance event and festival revenue | SAM $800M — dance ticketing digital platforms | SOM $120M — NFT ticket adopters in dance festivals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance NFT Tickets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sell and verify exclusive dance event tickets as tradable NFTs on-chain to prevent fraud. Discipline: Dance & Choreography (event access control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate issuance and verification of NFT tickets without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance NFT Tickets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movecoin-rewards-2 Title: MoveCoin Rewards Theme: Dance & Choreography (dance) · incentive tokenization Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Reward dancers with crypto tokens for participation and achievements in studio classes. Why Hedera: Onchain tokens enable transparent, automatic reward distribution and tracking. Market: TAM $5B — global dance studio software | SAM $400M — dance education digital tools | SOM $30M — token reward platforms in fitness and dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoveCoin Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward dancers with crypto tokens for participation and achievements in studio classes. Discipline: Dance & Choreography (incentive tokenization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokens enable transparent, automatic reward distribution and tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoveCoin Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreo-royalty-pool-3 Title: Choreo Royalty Pool Theme: Dance & Choreography (dance) · rights management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Distribute royalties automatically among choreographers, dancers, and producers using smart contracts. Why Hedera: Hedera testnet contracts transparently split and enforce royalty terms without intermediaries. Market: TAM $5B — global dance content monetization | SAM $1B — digital dance media licensing | SOM $80M — royalty management platforms in dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo Royalty Pool" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute royalties automatically among choreographers, dancers, and producers using smart contracts. Discipline: Dance & Choreography (rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts transparently split and enforce royalty terms without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo Royalty Pool" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-syncstage-auction-4 Title: SyncStage Auction Theme: Dance & Choreography (dance) · live collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Auction live dance performance slots to global choreographers via transparent smart contracts. Why Hedera: Smart contracts enable trustless, automated auction and booking processes worldwide. Market: TAM $5B — global dance performance market | SAM $600M — live dance booking platforms | SOM $70M — tech-enabled dance collaboration tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SyncStage Auction" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Auction live dance performance slots to global choreographers via transparent smart contracts. Discipline: Dance & Choreography (live collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable trustless, automated auction and booking processes worldwide. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SyncStage Auction" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-moveproof-ledger-5 Title: MoveProof Ledger Theme: Dance & Choreography (dance) · movement authenticity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record and verify unique dance moves proof-of-existence onchain to counter plagiarism. Why Hedera: Immutable smart contracts provide tamper-proof timestamps for movement intellectual property. Market: TAM $5B — global dance IP management | SAM $350M — digital dance content rights | SOM $25M — early adopters of authenticity verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoveProof Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and verify unique dance moves proof-of-existence onchain to counter plagiarism. Discipline: Dance & Choreography (movement authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable smart contracts provide tamper-proof timestamps for movement intellectual property. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoveProof Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-tokenized-choreo-kits-6 Title: Tokenized Choreo Kits Theme: Dance & Choreography (dance) · content packaging Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Sell bundled choreography sequences as tokenized assets for easier licensing and resale. Why Hedera: Hedera testnet smart contracts facilitate fractional ownership and transparent transfers. Market: TAM $5B — dance content licensing | SAM $450M — digital choreography sales | SOM $40M — platforms enabling dance content resale ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Choreo Kits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sell bundled choreography sequences as tokenized assets for easier licensing and resale. Discipline: Dance & Choreography (content packaging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts facilitate fractional ownership and transparent transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Choreo Kits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dancedao-collective-7 Title: DanceDAO Collective Theme: Dance & Choreography (dance) · community governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable dance groups to govern studios and projects democratically via onchain voting. Why Hedera: Smart contracts ensure transparent, tamper-proof decentralized decision-making. Market: TAM $5B — global dance studios management | SAM $300M — dance organization software | SOM $20M — DAO-powered dance communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceDAO Collective" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable dance groups to govern studios and projects democratically via onchain voting. Discipline: Dance & Choreography (community governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts ensure transparent, tamper-proof decentralized decision-making. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceDAO Collective" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-nft-move-badges-8 Title: NFT Move Badges Theme: Dance & Choreography (dance) · achievement tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue unique NFT badges for dance milestones and skills to motivate learners. Why Hedera: Onchain issuance guarantees badge uniqueness and verifiability worldwide. Market: TAM $5B — dance education market | SAM $250M — digital learning credentials | SOM $15M — NFT-based achievement tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Move Badges" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue unique NFT badges for dance milestones and skills to motivate learners. Discipline: Dance & Choreography (achievement tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain issuance guarantees badge uniqueness and verifiability worldwide. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Move Badges" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreo-licensing-hub-9 Title: Choreo Licensing Hub Theme: Dance & Choreography (dance) · rights marketplace Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create a decentralized marketplace for buying and selling choreography licenses directly onchain. Why Hedera: Smart contracts automate license agreements and transparent payments without middlemen. Market: TAM $5B — dance IP licensing | SAM $700M — choreography rights platforms | SOM $55M — blockchain-enabled license marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo Licensing Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a decentralized marketplace for buying and selling choreography licenses directly onchain. Discipline: Dance & Choreography (rights marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate license agreements and transparent payments without middlemen. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo Licensing Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dancemove-provenance-10 Title: DanceMove Provenance Theme: Dance & Choreography (dance) · creative lineage Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Trace and display the lineage and remix history of dance moves on an immutable ledger. Why Hedera: Hedera testnet contracts track ownership and modification history transparently and permanently. Market: TAM $5B — global dance creative IP | SAM $400M — dance content provenance tools | SOM $30M — provenance tracking adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceMove Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trace and display the lineage and remix history of dance moves on an immutable ledger. Discipline: Dance & Choreography (creative lineage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts track ownership and modification history transparently and permanently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceMove Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-livemotion-payments-11 Title: LiveMotion Payments Theme: Dance & Choreography (dance) · instant settlement Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Settle payments instantly for live dance services and gigs through smart contracts. Why Hedera: Onchain escrow and release ensures trustless, real-time transaction finality. Market: TAM $5B — global dance gig economy | SAM $600M — dance freelance payment platforms | SOM $50M — blockchain payment adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LiveMotion Payments" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Settle payments instantly for live dance services and gigs through smart contracts. Discipline: Dance & Choreography (instant settlement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain escrow and release ensures trustless, real-time transaction finality. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LiveMotion Payments" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreocrowd-fund-12 Title: ChoreoCrowd Fund Theme: Dance & Choreography (dance) · project crowdfunding Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable transparent onchain fundraising for dance projects with milestones and payout automation. Why Hedera: Smart contracts enforce conditions and ensure proper fund distribution to creators. Market: TAM $5B — global dance production funding | SAM $200M — dance crowdfunding platforms | SOM $18M — blockchain crowdfunding adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoCrowd Fund" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable transparent onchain fundraising for dance projects with milestones and payout automation. Discipline: Dance & Choreography (project crowdfunding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enforce conditions and ensure proper fund distribution to creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoCrowd Fund" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dancestep-identity-13 Title: DanceStep Identity Theme: Dance & Choreography (dance) · digital identity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create verifiable digital identities for dancers' skills and experiences stored onchain. Why Hedera: Immutable smart contracts secure skill records accessible globally by studios and agents. Market: TAM $5B — dance workforce management | SAM $220M — digital talent verification | SOM $20M — digital identity adopters in dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceStep Identity" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create verifiable digital identities for dancers' skills and experiences stored onchain. Discipline: Dance & Choreography (digital identity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable smart contracts secure skill records accessible globally by studios and agents. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceStep Identity" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreoswap-marketplace-14 Title: ChoreoSwap Marketplace Theme: Dance & Choreography (dance) · content exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Swap choreography clips and movement motifs peer-to-peer as tokenized assets onchain. Why Hedera: Smart contracts enable trustless, instant exchange and ownership transfer without intermediaries. Market: TAM $5B — global dance content exchange | SAM $480M — digital choreography marketplaces | SOM $35M — blockchain content trading users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoSwap Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Swap choreography clips and movement motifs peer-to-peer as tokenized assets onchain. Discipline: Dance & Choreography (content exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable trustless, instant exchange and ownership transfer without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoSwap Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movemint-workshops-15 Title: MoveMint Workshops Theme: Dance & Choreography (dance) · tokenized education Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint limited access tokens to exclusive dance workshops and masterclasses as NFTs. Why Hedera: Onchain NFT tickets guarantee authenticity and scarcity for premium educational content. Market: TAM $5B — dance education market | SAM $350M — digital workshop platforms | SOM $25M — NFT ticket users in dance education ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoveMint Workshops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint limited access tokens to exclusive dance workshops and masterclasses as NFTs. Discipline: Dance & Choreography (tokenized education). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFT tickets guarantee authenticity and scarcity for premium educational content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoveMint Workshops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-stepchain-voting-16 Title: StepChain Voting Theme: Dance & Choreography (dance) · event curation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Let audiences vote transparently and fairly on dance competition outcomes via smart contracts. Why Hedera: Hedera testnet contracts provide immutable, verifiable vote tallying without manipulation. Market: TAM $5B — dance event market | SAM $300M — digital event management tools | SOM $22M — blockchain voting adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StepChain Voting" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Let audiences vote transparently and fairly on dance competition outcomes via smart contracts. Discipline: Dance & Choreography (event curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide immutable, verifiable vote tallying without manipulation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StepChain Voting" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreoproof-licensing-17 Title: ChoreoProof Licensing Theme: Dance & Choreography (dance) · automated contracts Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate licensing agreements for choreography use with onchain smart contract enforcement. Why Hedera: Smart contracts reduce disputes by executing terms exactly as coded without intermediaries. Market: TAM $5B — dance IP licensing | SAM $650M — contract automation platforms | SOM $45M — onchain licensing adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoProof Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate licensing agreements for choreography use with onchain smart contract enforcement. Discipline: Dance & Choreography (automated contracts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts reduce disputes by executing terms exactly as coded without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoProof Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dancetoken-staking-18 Title: DanceToken Staking Theme: Dance & Choreography (dance) · community incentives Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Stake tokens to support favorite dancers and receive exclusive perks or content rewards. Why Hedera: Onchain staking programs enable transparent, programmable community funding and rewards. Market: TAM $5B — dance fan engagement market | SAM $270M — digital fan token economies | SOM $20M — token staking communities in dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceToken Staking" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Stake tokens to support favorite dancers and receive exclusive perks or content rewards. Discipline: Dance & Choreography (community incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain staking programs enable transparent, programmable community funding and rewards. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceToken Staking" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-stepswap-royalties-19 Title: StepSwap Royalties Theme: Dance & Choreography (dance) · resale tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and enforce royalty payments on resales of choreography NFTs via smart contracts. Why Hedera: Hedera testnet smart contracts automatically route royalties ensuring creator revenue continuity. Market: TAM $5B — digital dance IP market | SAM $500M — NFT resale royalty management | SOM $40M — active royalty enforcement users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StepSwap Royalties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and enforce royalty payments on resales of choreography NFTs via smart contracts. Discipline: Dance & Choreography (resale tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts automatically route royalties ensuring creator revenue continuity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StepSwap Royalties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-danceledger-archives-20 Title: DanceLedger Archives Theme: Dance & Choreography (dance) · historical record Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Archive dance event records and performances permanently on a public blockchain ledger. Why Hedera: Immutable data storage preserves cultural heritage accessible globally forever. Market: TAM $5B — dance media archives | SAM $300M — digital archival solutions | SOM $25M — blockchain archival adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceLedger Archives" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive dance event records and performances permanently on a public blockchain ledger. Discipline: Dance & Choreography (historical record). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable data storage preserves cultural heritage accessible globally forever. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceLedger Archives" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreominting-platform-21 Title: ChoreoMinting Platform Theme: Dance & Choreography (dance) · NFT creation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Empower dancers to mint and sell unique choreography NFTs directly from studios. Why Hedera: Onchain minting supports decentralized content ownership and monetization by creators. Market: TAM $5B — digital dance content market | SAM $600M — NFT creation tools for dance | SOM $50M — dance NFT creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoMinting Platform" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Empower dancers to mint and sell unique choreography NFTs directly from studios. Discipline: Dance & Choreography (NFT creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain minting supports decentralized content ownership and monetization by creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoMinting Platform" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-tokenmove-sponsorship-22 Title: TokenMove Sponsorship Theme: Dance & Choreography (dance) · brand engagement Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable brands to sponsor dancers directly through programmable tokens and onchain contracts. Why Hedera: Smart contracts transparently automate sponsor payments and benefits with auditability. Market: TAM $5B — dance sponsorship market | SAM $400M — dance-brand partnership platforms | SOM $30M — blockchain-enabled sponsorship users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TokenMove Sponsorship" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable brands to sponsor dancers directly through programmable tokens and onchain contracts. Discipline: Dance & Choreography (brand engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts transparently automate sponsor payments and benefits with auditability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TokenMove Sponsorship" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-crowddance-licensing-23 Title: CrowdDance Licensing Theme: Dance & Choreography (dance) · mass collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: License remixed choreography created by crowdsourced dancers with onchain rights management. Why Hedera: Smart contracts coordinate multi-owner rights and revenue splitting efficiently and fairly. Market: TAM $5B — collaborative dance content market | SAM $350M — group licensing platforms | SOM $28M — blockchain group rights users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CrowdDance Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License remixed choreography created by crowdsourced dancers with onchain rights management. Discipline: Dance & Choreography (mass collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts coordinate multi-owner rights and revenue splitting efficiently and fairly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CrowdDance Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-danceswap-collaborations-24 Title: DanceSwap Collaborations Theme: Dance & Choreography (dance) · peer collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Facilitate peer-to-peer choreography collaboration and revenue sharing via smart contracts. Why Hedera: Onchain contracts ensure trustless cooperation and fair profit distribution among artists. Market: TAM $5B — global dance collaboration market | SAM $400M — collaborative creation platforms | SOM $30M — blockchain collaboration adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceSwap Collaborations" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate peer-to-peer choreography collaboration and revenue sharing via smart contracts. Discipline: Dance & Choreography (peer collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain contracts ensure trustless cooperation and fair profit distribution among artists. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceSwap Collaborations" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreo-sequence-vault-0 Title: Choreo Sequence Vault Theme: Dance & Choreography (dance) · dance notation archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely store and share choreographic sequences as immutable IPFS manifests for global access. Why Hedera: IPFS ensures permanent, tamper-proof storage of choreographic data accessible anywhere. Market: TAM $5B — global dance studio software | SAM $1B — choreography archiving tools | SOM $100M — cloud storage for dance educators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo Sequence Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and share choreographic sequences as immutable IPFS manifests for global access. Discipline: Dance & Choreography (dance notation archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures permanent, tamper-proof storage of choreographic data accessible anywhere. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo Sequence Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-moodboard-1 Title: Movement Moodboard Theme: Dance & Choreography (dance) · dance inspiration boards Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create and pin curated dance inspiration boards with images and JSON metadata on IPFS. Why Hedera: Pinata JWT uploads enable decentralized and permanent preservation of moodboards. Market: TAM $5B — global dance studio software | SAM $300M — digital moodboard platforms | SOM $30M — dance teacher resources ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Moodboard" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and pin curated dance inspiration boards with images and JSON metadata on IPFS. Discipline: Dance & Choreography (dance inspiration boards). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT uploads enable decentralized and permanent preservation of moodboards. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Moodboard" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-nft-gallery-2 Title: Dance NFT Gallery Theme: Dance & Choreography (dance) · digital dance art Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Artists mint and pin digital dance art portfolios as NFTs stored forever on IPFS. Why Hedera: Pinata’s IPFS pinning guarantees permanent availability of NFT metadata and artworks. Market: TAM $5B — global dance industry digital products | SAM $500M — dance-related NFTs | SOM $50M — niche dance digital collectibles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance NFT Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Artists mint and pin digital dance art portfolios as NFTs stored forever on IPFS. Discipline: Dance & Choreography (digital dance art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning guarantees permanent availability of NFT metadata and artworks. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance NFT Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreographer-s-ledger-3 Title: Choreographer’s Ledger Theme: Dance & Choreography (dance) · IP rights management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Record choreography permissions and usage rights as JSON manifests pinned on IPFS. Why Hedera: IPFS immutability ensures proof of IP ownership and permission records. Market: TAM $5B — global dance IP management | SAM $800M — choreography licensing software | SOM $70M — dance rights tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreographer’s Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record choreography permissions and usage rights as JSON manifests pinned on IPFS. Discipline: Dance & Choreography (IP rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS immutability ensures proof of IP ownership and permission records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreographer’s Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-technique-archive-hub-4 Title: Technique Archive Hub Theme: Dance & Choreography (dance) · dance training materials Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and share detailed dance technique manuals and videos as permanent IPFS content. Why Hedera: IPFS provides a censorship-resistant platform for long-term educational content storage. Market: TAM $5B — dance education market | SAM $600M — online dance training resources | SOM $50M — technique tutorial distribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Technique Archive Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and share detailed dance technique manuals and videos as permanent IPFS content. Discipline: Dance & Choreography (dance training materials). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides a censorship-resistant platform for long-term educational content storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Technique Archive Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-metadata-mapper-5 Title: Movement Metadata Mapper Theme: Dance & Choreography (dance) · dance data annotation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Attach rich JSON metadata to dance performance images pinned via IPFS for enhanced searchability. Why Hedera: IPFS stores immutable annotated datasets accessible globally without central servers. Market: TAM $5B — dance analytics services | SAM $200M — metadata management tools | SOM $20M — dance performance data platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Metadata Mapper" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach rich JSON metadata to dance performance images pinned via IPFS for enhanced searchability. Discipline: Dance & Choreography (dance data annotation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores immutable annotated datasets accessible globally without central servers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Metadata Mapper" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-global-dance-archive-6 Title: Global Dance Archive Theme: Dance & Choreography (dance) · dance heritage preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Digitize and pin cultural dance archives on IPFS preserving global dance heritage forever. Why Hedera: Pinata’s IPFS ensures permanent, decentralized custody of invaluable dance history data. Market: TAM $5B — cultural heritage digitization | SAM $400M — dance archive initiatives | SOM $40M — dance museum digital collections ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Global Dance Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitize and pin cultural dance archives on IPFS preserving global dance heritage forever. Discipline: Dance & Choreography (dance heritage preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS ensures permanent, decentralized custody of invaluable dance history data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Global Dance Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-collaborative-choreo-file-7 Title: Collaborative Choreo File Theme: Dance & Choreography (dance) · group choreography workflow Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Teams pin evolving choreography JSON manifests on IPFS for real-time, decentralized collaboration. Why Hedera: IPFS allows versioned, immutable storage accessible to all team members without downtime. Market: TAM $5B — dance collaboration tools | SAM $150M — workflow management apps | SOM $15M — choreographer team software ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Choreo File" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Teams pin evolving choreography JSON manifests on IPFS for real-time, decentralized collaboration. Discipline: Dance & Choreography (group choreography workflow). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS allows versioned, immutable storage accessible to all team members without downtime. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Choreo File" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-move-tokens-8 Title: Dance Move Tokens Theme: Dance & Choreography (dance) · movement licensing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create, pin, and trade unique dance moves with JSON metadata on IPFS representing rights. Why Hedera: Pinata facilitates permanent off-chain storage of token metadata ensuring rights transparency. Market: TAM $5B — dance IP monetization | SAM $400M — dance move marketplaces | SOM $35M — digital dance asset trading ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Move Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create, pin, and trade unique dance moves with JSON metadata on IPFS representing rights. Discipline: Dance & Choreography (movement licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata facilitates permanent off-chain storage of token metadata ensuring rights transparency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Move Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-virtual-rehearsal-logs-9 Title: Virtual Rehearsal Logs Theme: Dance & Choreography (dance) · practice session tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin JSON logs of rehearsal notes and images on IPFS for permanent, shareable practice records. Why Hedera: IPFS ensures immutable, censorship-resistant rehearsal documentation accessible anytime. Market: TAM $5B — dance training tools | SAM $100M — rehearsal management software | SOM $10M — dancer practice tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Rehearsal Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin JSON logs of rehearsal notes and images on IPFS for permanent, shareable practice records. Discipline: Dance & Choreography (practice session tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures immutable, censorship-resistant rehearsal documentation accessible anytime. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Rehearsal Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-interactive-dance-maps-10 Title: Interactive Dance Maps Theme: Dance & Choreography (dance) · movement spatialization Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin visual and JSON data of choreography spatial maps on IPFS for decentralized access and reuse. Why Hedera: IPFS stores complex multimedia choreography spatializations without central failures. Market: TAM $5B — dance technology market | SAM $180M — spatial choreography tools | SOM $18M — movement mapping software ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Dance Maps" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin visual and JSON data of choreography spatial maps on IPFS for decentralized access and reuse. Discipline: Dance & Choreography (movement spatialization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores complex multimedia choreography spatializations without central failures. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Dance Maps" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-ai-dance-dataset-repository-11 Title: AI Dance Dataset Repository Theme: Dance & Choreography (dance) · machine learning data Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin curated datasets of labeled dance images and JSON metadata on IPFS for AI training. Why Hedera: IPFS guarantees permanent, verifiable storage of valuable AI training data. Market: TAM $5B — AI dance innovation | SAM $250M — dance AI datasets | SOM $25M — machine learning data hubs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AI Dance Dataset Repository" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin curated datasets of labeled dance images and JSON metadata on IPFS for AI training. Discipline: Dance & Choreography (machine learning data). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees permanent, verifiable storage of valuable AI training data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AI Dance Dataset Repository" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-festival-archive-12 Title: Dance Festival Archive Theme: Dance & Choreography (dance) · event documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin photos and JSON manifests of live dance festival performances on IPFS for posterity and access. Why Hedera: Pinata enables permanent, decentralized storage preventing loss of live event records. Market: TAM $5B — global dance event market | SAM $300M — festival media archiving | SOM $30M — digital dance event libraries ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Festival Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin photos and JSON manifests of live dance festival performances on IPFS for posterity and access. Discipline: Dance & Choreography (event documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables permanent, decentralized storage preventing loss of live event records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Festival Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-costume-design-catalog-13 Title: Costume Design Catalog Theme: Dance & Choreography (dance) · dance wardrobe archives Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin images and JSON design manifests of dance costumes on IPFS for long-term design preservation. Why Hedera: IPFS provides immutable, decentralized storage ensuring no loss of costume records. Market: TAM $5B — dance production resources | SAM $150M — costume design archives | SOM $12M — dance wardrobe databases ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Costume Design Catalog" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin images and JSON design manifests of dance costumes on IPFS for long-term design preservation. Discipline: Dance & Choreography (dance wardrobe archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides immutable, decentralized storage ensuring no loss of costume records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Costume Design Catalog" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-emotion-tags-14 Title: Movement Emotion Tags Theme: Dance & Choreography (dance) · affective annotation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin emotionally tagged dance movement metadata on IPFS to enhance creative interpretative tools. Why Hedera: IPFS stores richly annotated JSON ensuring permanent availability for emotion-based indexing. Market: TAM $5B — dance tech innovation | SAM $100M — affective computing tools | SOM $11M — emotion annotation tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Emotion Tags" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin emotionally tagged dance movement metadata on IPFS to enhance creative interpretative tools. Discipline: Dance & Choreography (affective annotation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores richly annotated JSON ensuring permanent availability for emotion-based indexing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Emotion Tags" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-challenge-repository-15 Title: Dance Challenge Repository Theme: Dance & Choreography (dance) · community choreography Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin community dance challenge videos and JSON instructions on IPFS for global participation. Why Hedera: IPFS enables permanent, censorship-resistant hosting of viral dance challenges globally. Market: TAM $5B — social dance platforms | SAM $350M — online dance communities | SOM $40M — challenge content hosting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Challenge Repository" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin community dance challenge videos and JSON instructions on IPFS for global participation. Discipline: Dance & Choreography (community choreography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS enables permanent, censorship-resistant hosting of viral dance challenges globally. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Challenge Repository" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-style-index-16 Title: Movement Style Index Theme: Dance & Choreography (dance) · genre classification Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin genre-classified dance move datasets with JSON metadata on IPFS for style machine parsing. Why Hedera: IPFS stores large stylistic datasets immutably accessible for genre recognition algorithms. Market: TAM $5B — dance analytics | SAM $220M — style classification tools | SOM $22M — dance data indexing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Style Index" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin genre-classified dance move datasets with JSON metadata on IPFS for style machine parsing. Discipline: Dance & Choreography (genre classification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores large stylistic datasets immutably accessible for genre recognition algorithms. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Style Index" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-live-choreo-snapshot-17 Title: Live Choreo Snapshot Theme: Dance & Choreography (dance) · performance capture Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin image and JSON snapshots of live choreography moments on IPFS for historical timestamping. Why Hedera: IPFS ensures permanent, verifiable storage of live performance digital artifacts. Market: TAM $5B — live performance tech | SAM $180M — performance capture tools | SOM $15M — dance show archiving ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Live Choreo Snapshot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin image and JSON snapshots of live choreography moments on IPFS for historical timestamping. Discipline: Dance & Choreography (performance capture). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures permanent, verifiable storage of live performance digital artifacts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Live Choreo Snapshot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-studio-portfolio-18 Title: Dance Studio Portfolio Theme: Dance & Choreography (dance) · business marketing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Studios pin portfolios of images and JSON class manifests on IPFS to showcase offerings forever. Why Hedera: IPFS secures studio marketing materials with permanent, decentralized hosting. Market: TAM $5B — dance studio software | SAM $500M — studio marketing platforms | SOM $50M — dance business portfolios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Studio Portfolio" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Studios pin portfolios of images and JSON class manifests on IPFS to showcase offerings forever. Discipline: Dance & Choreography (business marketing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS secures studio marketing materials with permanent, decentralized hosting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Studio Portfolio" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreography-remix-log-19 Title: Choreography Remix Log Theme: Dance & Choreography (dance) · creative versioning Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin JSON manifests of choreographic remixes and images on IPFS to document creative lineage. Why Hedera: IPFS allows immutable, traceable version histories of choreography remixes. Market: TAM $5B — dance creative tools | SAM $200M — choreography version control | SOM $18M — remix documentation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreography Remix Log" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin JSON manifests of choreographic remixes and images on IPFS to document creative lineage. Discipline: Dance & Choreography (creative versioning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS allows immutable, traceable version histories of choreography remixes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreography Remix Log" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-props-inventory-20 Title: Dance Props Inventory Theme: Dance & Choreography (dance) · production resource management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin images and JSON manifests of dance prop inventories to IPFS for decentralized sharing. Why Hedera: IPFS guarantees permanent, accessible storage of production resource data. Market: TAM $5B — dance production management | SAM $120M — prop rental platforms | SOM $10M — studio inventory systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Props Inventory" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin images and JSON manifests of dance prop inventories to IPFS for decentralized sharing. Discipline: Dance & Choreography (production resource management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees permanent, accessible storage of production resource data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Props Inventory" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-annotation-tool-21 Title: Movement Annotation Tool Theme: Dance & Choreography (dance) · dance research Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin annotated movement images and JSON metadata on IPFS to support global dance research. Why Hedera: IPFS provides immutable, globally accessible data for academic and artistic analysis. Market: TAM $5B — dance research tools | SAM $80M — annotation software | SOM $8M — academic dance databases ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Annotation Tool" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin annotated movement images and JSON metadata on IPFS to support global dance research. Discipline: Dance & Choreography (dance research). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides immutable, globally accessible data for academic and artistic analysis. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Annotation Tool" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-therapy-logs-22 Title: Dance Therapy Logs Theme: Dance & Choreography (dance) · health documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin JSON and image records of dance therapy sessions on IPFS for secure patient histories. Why Hedera: IPFS ensures confidential, permanent, and decentralized health session storage. Market: TAM $5B — dance therapy market | SAM $90M — therapy practice software | SOM $9M — patient session archiving ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Therapy Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin JSON and image records of dance therapy sessions on IPFS for secure patient histories. Discipline: Dance & Choreography (health documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures confidential, permanent, and decentralized health session storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Therapy Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-interactive-step-manuals-23 Title: Interactive Step Manuals Theme: Dance & Choreography (dance) · teaching aids Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin interactive dance step image guides plus JSON instructions on IPFS for teacher-student use. Why Hedera: IPFS supports permanent, decentralized hosting of teaching content accessible offline. Market: TAM $5B — dance education | SAM $400M — teaching resource platforms | SOM $35M — dance manual distribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Step Manuals" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin interactive dance step image guides plus JSON instructions on IPFS for teacher-student use. Discipline: Dance & Choreography (teaching aids). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS supports permanent, decentralized hosting of teaching content accessible offline. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Step Manuals" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-patent-registry-24 Title: Movement Patent Registry Theme: Dance & Choreography (dance) · innovation tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin JSON manifests documenting novel dance moves on IPFS to certify original creation dates. Why Hedera: IPFS provides immutable timestamped records critical for innovation proofing. Market: TAM $5B — dance IP protection | SAM $300M — innovation registries | SOM $28M — dance patent recordkeeping ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Patent Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin JSON manifests documenting novel dance moves on IPFS to certify original creation dates. Discipline: Dance & Choreography (innovation tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides immutable timestamped records critical for innovation proofing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Patent Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gasless-dance-battles-0 Title: Gasless Dance Battles Theme: Dance & Choreography (dance) · competitive choreography Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable dancers to join and sponsor dance battles without paying gas fees. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees removes friction of blockchain fees in quick peer competitions. Market: TAM $5B — global dance industry revenue | SAM $600M — online dance battle platforms | SOM $30M — active dance battle participants paying digitally ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Dance Battles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable dancers to join and sponsor dance battles without paying gas fees. Discipline: Dance & Choreography (competitive choreography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees removes friction of blockchain fees in quick peer competitions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Dance Battles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreo-nft-vault-1 Title: Choreo NFT Vault Theme: Dance & Choreography (dance) · digital choreography ownership Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely store and share choreography as NFTs without users handling gas payments. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees eases NFT minting experience. Market: TAM $5B — global dance content monetization | SAM $800M — dance NFT market segment | SOM $50M — choreographers minting digital rights ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo NFT Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and share choreography as NFTs without users handling gas payments. Discipline: Dance & Choreography (digital choreography ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees eases NFT minting experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo NFT Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-studio-access-club-2 Title: Studio Access Club Theme: Dance & Choreography (dance) · membership management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create token-gated memberships for dance studios with seamless gas-free onboarding. Why Hedera: Embedded wallet and Hedera's fixed sub-cent fees simplify user entry and retention. Market: TAM $5B — global dance studio operations | SAM $1B — dance studio subscription services | SOM $60M — studios adopting digital memberships ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Studio Access Club" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create token-gated memberships for dance studios with seamless gas-free onboarding. Discipline: Dance & Choreography (membership management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Embedded wallet and Hedera's fixed sub-cent fees simplify user entry and retention. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Studio Access Club" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-royalty-tracker-3 Title: Movement Royalty Tracker Theme: Dance & Choreography (dance) · usage rights Hedera hook: Magic Link email wallet [wallet UX] Pitch: Automatically track and reward choreographers when their moves get used commercially. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable transparent royalty distribution without gas burden. Market: TAM $5B — dance content licensing | SAM $700M — choreography rights market | SOM $40M — royalty payments automated ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Royalty Tracker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically track and reward choreographers when their moves get used commercially. Discipline: Dance & Choreography (usage rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable transparent royalty distribution without gas burden. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Royalty Tracker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gas-free-dance-voting-4 Title: Gas-Free Dance Voting Theme: Dance & Choreography (dance) · community decision-making Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host dance community polls and awards with zero gas cost for voters. Why Hedera: Google sign-in and Hedera's fixed sub-cent fees streamline inclusive community governance. Market: TAM $5B — global dance industry engagement | SAM $500M — digital dance community activities | SOM $25M — community voting participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Dance Voting" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host dance community polls and awards with zero gas cost for voters. Discipline: Dance & Choreography (community decision-making). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Google sign-in and Hedera's fixed sub-cent fees streamline inclusive community governance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Dance Voting" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreo-collaboration-hub-5 Title: Choreo Collaboration Hub Theme: Dance & Choreography (dance) · joint choreography creation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Facilitate collaborative dance projects with blockchain-secured contribution records, gas-free. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable seamless multi-user editing without crypto hurdles. Market: TAM $5B — dance creative projects | SAM $650M — collaborative dance platforms | SOM $35M — choreographers collaborating online ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo Collaboration Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate collaborative dance projects with blockchain-secured contribution records, gas-free. Discipline: Dance & Choreography (joint choreography creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable seamless multi-user editing without crypto hurdles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo Collaboration Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-event-tickets-6 Title: Dance Event Tickets Theme: Dance & Choreography (dance) · ticketing system Hedera hook: Magic Link email wallet [wallet UX] Pitch: Sell and distribute dance event tickets as NFTs with no gas for buyers. Why Hedera: Embedded wallet bootstrapped and Hedera's fixed sub-cent fees reduce friction in ticket ownership. Market: TAM $5B — dance live events | SAM $900M — digital event ticket sales | SOM $70M — NFT ticket adopters in dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Event Tickets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sell and distribute dance event tickets as NFTs with no gas for buyers. Discipline: Dance & Choreography (ticketing system). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Embedded wallet bootstrapped and Hedera's fixed sub-cent fees reduce friction in ticket ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Event Tickets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gasless-dance-royalties-7 Title: Gasless Dance Royalties Theme: Dance & Choreography (dance) · automatic payments Hedera hook: Magic Link email wallet [wallet UX] Pitch: Pay royalties to dancers and choreographers instantly with no transaction fees. Why Hedera: Hedera's fixed sub-cent fees ensure fast, costless royalty distribution via Magic Link email sign-in. Market: TAM $5B — dance content monetization | SAM $750M — royalty payment platforms | SOM $45M — royalty payees using blockchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Dance Royalties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pay royalties to dancers and choreographers instantly with no transaction fees. Discipline: Dance & Choreography (automatic payments). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees ensure fast, costless royalty distribution via Magic Link email sign-in. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Dance Royalties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-move-provenance-8 Title: Dance Move Provenance Theme: Dance & Choreography (dance) · authenticity verification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Verify authenticity and origin of iconic dance moves onchain, gaslessly. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allow users to prove originality without gas costs. Market: TAM $5B — dance IP protection | SAM $550M — digital dance authenticity tools | SOM $28M — verified dance creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Move Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify authenticity and origin of iconic dance moves onchain, gaslessly. Discipline: Dance & Choreography (authenticity verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allow users to prove originality without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Move Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gas-free-dance-workshops-9 Title: Gas-Free Dance Workshops Theme: Dance & Choreography (dance) · online learning Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enroll and attend dance workshops with blockchain-secured certificates, no gas fees. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees improve user onboarding and certificate issuance. Market: TAM $5B — dance education market | SAM $1.2B — online dance classes | SOM $80M — blockchain-certified workshop attendees ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Dance Workshops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enroll and attend dance workshops with blockchain-secured certificates, no gas fees. Discipline: Dance & Choreography (online learning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees improve user onboarding and certificate issuance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Dance Workshops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreo-token-rewards-10 Title: Choreo Token Rewards Theme: Dance & Choreography (dance) · creator incentives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward choreographers with tokens for popular routines without requiring gas payments. Why Hedera: Embedded wallet and Hedera's fixed sub-cent fees streamline token distribution to creators. Market: TAM $5B — dance creator economy | SAM $600M — dance tokenized incentives | SOM $35M — rewarded choreographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreo Token Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward choreographers with tokens for popular routines without requiring gas payments. Discipline: Dance & Choreography (creator incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Embedded wallet and Hedera's fixed sub-cent fees streamline token distribution to creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreo Token Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-gear-marketplace-11 Title: Dance Gear Marketplace Theme: Dance & Choreography (dance) · digital commerce Hedera hook: Magic Link email wallet [wallet UX] Pitch: Buy and sell dance apparel and gear as NFTs with gasless transaction experience. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable smooth gas-free commerce. Market: TAM $5B — dance retail and merchandise | SAM $700M — digital dance gear sales | SOM $40M — NFT marketplace participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance Gear Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Buy and sell dance apparel and gear as NFTs with gasless transaction experience. Discipline: Dance & Choreography (digital commerce). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable smooth gas-free commerce. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance Gear Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-data-wallet-12 Title: Movement Data Wallet Theme: Dance & Choreography (dance) · performance analytics Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely store and share movement data NFTs with no blockchain fees for dancers. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure easy access and sharing of data. Market: TAM $5B — dance tech analytics market | SAM $400M — movement analytics services | SOM $22M — dancers using data NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Data Wallet" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and share movement data NFTs with no blockchain fees for dancers. Discipline: Dance & Choreography (performance analytics). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure easy access and sharing of data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Data Wallet" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gasless-dance-challenges-13 Title: Gasless Dance Challenges Theme: Dance & Choreography (dance) · social engagement Hedera hook: Magic Link email wallet [wallet UX] Pitch: Launch viral dance challenges with onchain proof and no gas cost for participants. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees maximize participation by eliminating fees. Market: TAM $5B — dance social media influence | SAM $500M — dance challenge platforms | SOM $30M — active challenge participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Dance Challenges" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Launch viral dance challenges with onchain proof and no gas cost for participants. Discipline: Dance & Choreography (social engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees maximize participation by eliminating fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Dance Challenges" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-nft-gallery-14 Title: Dance NFT Gallery Theme: Dance & Choreography (dance) · exhibition curation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host curated dance NFT galleries that users can explore without paying gas. Why Hedera: Google sign-in based wallet plus Hedera's fixed sub-cent fees simplify visitor experience. Market: TAM $5B — dance digital art exhibitions | SAM $450M — online dance galleries | SOM $27M — NFT gallery visitors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance NFT Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host curated dance NFT galleries that users can explore without paying gas. Discipline: Dance & Choreography (exhibition curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Google sign-in based wallet plus Hedera's fixed sub-cent fees simplify visitor experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance NFT Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreography-licensing-hub-15 Title: Choreography Licensing Hub Theme: Dance & Choreography (dance) · rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: License dance routines with transparent blockchain contracts and gasless transactions. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees reduces licensing friction. Market: TAM $5B — dance licensing market | SAM $800M — digital choreography licenses | SOM $50M — licensees using platform ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreography Licensing Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License dance routines with transparent blockchain contracts and gasless transactions. Discipline: Dance & Choreography (rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees reduces licensing friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreography Licensing Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gas-free-dance-nfts-16 Title: Gas-Free Dance NFTs Theme: Dance & Choreography (dance) · blockchain collectibles Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint and trade unique dance moment NFTs without gas fees for fans and artists. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees eliminate gas barriers for casual users. Market: TAM $5B — dance NFT collectibles | SAM $700M — dance-related NFT volume | SOM $40M — active dance NFT collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Dance NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade unique dance moment NFTs without gas fees for fans and artists. Discipline: Dance & Choreography (blockchain collectibles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees eliminate gas barriers for casual users. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Dance NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-virtual-dance-studios-17 Title: Virtual Dance Studios Theme: Dance & Choreography (dance) · metaverse dance spaces Hedera hook: Magic Link email wallet [wallet UX] Pitch: Access and customize virtual dance studios with gasless token gating. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees make metaverse entry seamless. Market: TAM $5B — dance virtual reality market | SAM $900M — virtual dance studio platforms | SOM $60M — active virtual studio users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Dance Studios" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Access and customize virtual dance studios with gasless token gating. Discipline: Dance & Choreography (metaverse dance spaces). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees make metaverse entry seamless. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Dance Studios" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-performance-ticket-nfts-18 Title: Performance Ticket NFTs Theme: Dance & Choreography (dance) · ticketing and access Hedera hook: Magic Link email wallet [wallet UX] Pitch: Issue and verify dance performance tickets as NFTs with no gas costs for attendees. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees simplify ticket ownership experience. Market: TAM $5B — dance live performance revenue | SAM $850M — NFT ticketing market | SOM $55M — NFT ticket users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Performance Ticket NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue and verify dance performance tickets as NFTs with no gas costs for attendees. Discipline: Dance & Choreography (ticketing and access). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees simplify ticket ownership experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Performance Ticket NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gasless-dance-socials-19 Title: Gasless Dance Socials Theme: Dance & Choreography (dance) · community building Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create dance-focused social networks with token rewards and no gas transaction fees. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees drive engagement with zero cost actions. Market: TAM $5B — dance social media market | SAM $600M — dance community platforms | SOM $33M — active social network users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Dance Socials" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create dance-focused social networks with token rewards and no gas transaction fees. Discipline: Dance & Choreography (community building). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees drive engagement with zero cost actions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Dance Socials" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreographer-profiles-20 Title: Choreographer Profiles Theme: Dance & Choreography (dance) · professional branding Hedera hook: Magic Link email wallet [wallet UX] Pitch: Build verified onchain profiles for choreographers with gasless updates and endorsements. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees streamline identity management. Market: TAM $5B — dance professional services | SAM $650M — online dancer portfolios | SOM $36M — choreographer profile users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreographer Profiles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Build verified onchain profiles for choreographers with gasless updates and endorsements. Discipline: Dance & Choreography (professional branding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees streamline identity management. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreographer Profiles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dance-nft-streaming-21 Title: Dance NFT Streaming Theme: Dance & Choreography (dance) · content distribution Hedera hook: Magic Link email wallet [wallet UX] Pitch: Stream exclusive dance content NFTs to fans without requiring gas payments. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees allow smooth access to paid streams. Market: TAM $5B — dance digital media market | SAM $700M — dance NFT streaming | SOM $42M — paying stream viewers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dance NFT Streaming" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Stream exclusive dance content NFTs to fans without requiring gas payments. Discipline: Dance & Choreography (content distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees allow smooth access to paid streams. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dance NFT Streaming" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movement-token-tips-22 Title: Movement Token Tips Theme: Dance & Choreography (dance) · fan support Hedera hook: Magic Link email wallet [wallet UX] Pitch: Let fans tip dancers instantly with tokens and no gas fees. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees remove barriers to microtransactions. Market: TAM $5B — dance fan economy | SAM $550M — digital tipping platforms | SOM $30M — active tip recipients ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Movement Token Tips" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Let fans tip dancers instantly with tokens and no gas fees. Discipline: Dance & Choreography (fan support). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees remove barriers to microtransactions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Movement Token Tips" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-gasless-dance-analytics-23 Title: Gasless Dance Analytics Theme: Dance & Choreography (dance) · performance insights Hedera hook: Magic Link email wallet [wallet UX] Pitch: Access detailed dance performance analytics with blockchain-backed data, no gas required. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees facilitate data queries at zero user cost. Market: TAM $5B — dance performance tech | SAM $450M — analytics subscription services | SOM $25M — users accessing data analytics ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Dance Analytics" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Access detailed dance performance analytics with blockchain-backed data, no gas required. Discipline: Dance & Choreography (performance insights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees facilitate data queries at zero user cost. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Dance Analytics" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-nft-dance-collectives-24 Title: NFT Dance Collectives Theme: Dance & Choreography (dance) · group ownership Hedera hook: Magic Link email wallet [wallet UX] Pitch: Form dance groups managing collective NFTs and revenue with gasless transactions. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable easy group wallet management. Market: TAM $5B — dance collaborative economy | SAM $500M — NFT group ownership market | SOM $28M — active dance collectives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Dance Collectives" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Form dance groups managing collective NFTs and revenue with gasless transactions. Discipline: Dance & Choreography (group ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable easy group wallet management. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Dance Collectives" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreochain-ledger-0 Title: ChoreoChain Ledger Theme: Dance & Choreography (dance) · movement cataloging Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and prove original dance moves as NFTs to protect choreographers' intellectual property. Why Hedera: NFT provenance ensures immutable proof of original creation stored onchain. Market: TAM $5B — global dance industry encompassing studios and creators | SAM $500M — digital choreography rights management sector | SOM $50M — early adopters of blockchain dance IP tools worldwide ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoChain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and prove original dance moves as NFTs to protect choreographers' intellectual property. Discipline: Dance & Choreography (movement cataloging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures immutable proof of original creation stored onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoChain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-flowmotion-archive-1 Title: FlowMotion Archive Theme: Dance & Choreography (dance) · dance notation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs representing detailed dance notations for preservation and easy sharing among dancers. Why Hedera: NFTs anchor IPFS-stored notation files with verifiable onchain ownership. Market: TAM $1B — dance education and archival resources market | SAM $120M — digital dance instruction content delivery | SOM $15M — NFT-based educational resource users in dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FlowMotion Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs representing detailed dance notations for preservation and easy sharing among dancers. Discipline: Dance & Choreography (dance notation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs anchor IPFS-stored notation files with verifiable onchain ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FlowMotion Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-beatsync-provenance-2 Title: BeatSync Provenance Theme: Dance & Choreography (dance) · rhythmic innovation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate unique dance beat patterns as NFTs for royalty-tracked reuse in choreography. Why Hedera: HTS NFT tokens link beats to creators with transparent ownership history. Market: TAM $3B — global dance music and rhythm licensing | SAM $300M — digital dance beat production and licensing | SOM $30M — blockchain-savvy dance music producers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "BeatSync Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate unique dance beat patterns as NFTs for royalty-tracked reuse in choreography. Discipline: Dance & Choreography (rhythmic innovation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link beats to creators with transparent ownership history. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "BeatSync Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-posemint-studio-3 Title: PoseMint Studio Theme: Dance & Choreography (dance) · pose collections Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFT collections of signature dance poses to license and showcase unique styles. Why Hedera: NFT minting guarantees original pose ownership and provenance onchain. Market: TAM $2B — global market for dance teaching and pose references | SAM $250M — digital pose libraries and tools | SOM $25M — NFT collectors and dance educators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoseMint Studio" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFT collections of signature dance poses to license and showcase unique styles. Discipline: Dance & Choreography (pose collections). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting guarantees original pose ownership and provenance onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoseMint Studio" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-steptrace-rights-4 Title: StepTrace Rights Theme: Dance & Choreography (dance) · step sequence rights Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint entire step sequences as NFTs to secure choreographic rights and facilitate licensing. Why Hedera: NFTs provide immutable records of step sequences with creator proofs. Market: TAM $4B — dance choreography licensing market | SAM $400M — digital choreography licensing platforms | SOM $40M — blockchain-based choreography licensing adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StepTrace Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint entire step sequences as NFTs to secure choreographic rights and facilitate licensing. Discipline: Dance & Choreography (step sequence rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide immutable records of step sequences with creator proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StepTrace Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-mirrormove-vault-5 Title: MirrorMove Vault Theme: Dance & Choreography (dance) · movement replication Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: NFT dance movement tokens enable secure sharing and replication tracking of signature moves. Why Hedera: NFTs offer tamper-proof proof of movement origin and transfer onchain. Market: TAM $3.5B — global dance movement sharing platforms | SAM $350M — digital dance content licensing | SOM $35M — early NFT movement replicators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MirrorMove Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT NFT dance movement tokens enable secure sharing and replication tracking of signature moves. Discipline: Dance & Choreography (movement replication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs offer tamper-proof proof of movement origin and transfer onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MirrorMove Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-rhythmroots-nft-6 Title: RhythmRoots NFT Theme: Dance & Choreography (dance) · cultural dance preservation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Preserve cultural dances as NFT-verified IPFS multimedia packages for global education. Why Hedera: NFT provenance protects cultural IP and ensures authenticity and respect. Market: TAM $2B — dance heritage and education market globally | SAM $200M — cultural dance digital archiving | SOM $20M — NFT cultural heritage educators and advocates ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RhythmRoots NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Preserve cultural dances as NFT-verified IPFS multimedia packages for global education. Discipline: Dance & Choreography (cultural dance preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance protects cultural IP and ensures authenticity and respect. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RhythmRoots NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-motionflow-tokens-7 Title: MotionFlow Tokens Theme: Dance & Choreography (dance) · dance flow sequences Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs of continuous dance flows that authenticate choreography progression and originality. Why Hedera: NFTs create unalterable records of flow sequences onchain for proof of authorship. Market: TAM $3B — dance choreography software and platforms | SAM $300M — dance flow digital content monetization | SOM $30M — NFT-savvy choreographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionFlow Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs of continuous dance flows that authenticate choreography progression and originality. Discipline: Dance & Choreography (dance flow sequences). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs create unalterable records of flow sequences onchain for proof of authorship. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionFlow Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-echodance-ledger-8 Title: EchoDance Ledger Theme: Dance & Choreography (dance) · dance remix rights Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Tokenize remix rights of dance routines as NFTs to facilitate fair use and royalties. Why Hedera: NFT provenance enables transparent chain of rights and remix permissions. Market: TAM $4.5B — global dance remix and derivative market | SAM $450M — digital dance remix licensing | SOM $45M — remixers using blockchain rights management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EchoDance Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize remix rights of dance routines as NFTs to facilitate fair use and royalties. Discipline: Dance & Choreography (dance remix rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance enables transparent chain of rights and remix permissions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EchoDance Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-stepsync-collectibles-9 Title: StepSync Collectibles Theme: Dance & Choreography (dance) · signature step NFTs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create collectible NFTs of iconic dance steps for fans and teaching accreditation. Why Hedera: Immutable NFTs certify ownership and authenticity of iconic dance moves. Market: TAM $2.5B — dance fan merchandise and collectibles | SAM $250M — digital collectible dance steps | SOM $25M — NFT dance collectibles market ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StepSync Collectibles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create collectible NFTs of iconic dance steps for fans and teaching accreditation. Discipline: Dance & Choreography (signature step NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFTs certify ownership and authenticity of iconic dance moves. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StepSync Collectibles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreochain-badge-10 Title: ChoreoChain Badge Theme: Dance & Choreography (dance) · certified choreography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Award NFT badges for verified choreography skills and professional credentials. Why Hedera: NFTs provide tamperproof certification linked to creator identity and HashScan verification. Market: TAM $1.8B — dance training and professional certification market | SAM $180M — digital certification platforms for dance | SOM $18M — blockchain-based skill verification users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoChain Badge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Award NFT badges for verified choreography skills and professional credentials. Discipline: Dance & Choreography (certified choreography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide tamperproof certification linked to creator identity and HashScan verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoChain Badge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dancedna-registry-11 Title: DanceDNA Registry Theme: Dance & Choreography (dance) · personal style encoding Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs encoding a dancer’s unique style ‘DNA’ for branding and licensing. Why Hedera: NFTs link personal style data stored on IPFS to a verifiable creator token. Market: TAM $2B — global dance branding and personal IP market | SAM $200M — digital dance personal brand assets | SOM $20M — blockchain-style trademark adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceDNA Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs encoding a dancer’s unique style ‘DNA’ for branding and licensing. Discipline: Dance & Choreography (personal style encoding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs link personal style data stored on IPFS to a verifiable creator token. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceDNA Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-pulseproof-archives-12 Title: PulseProof Archives Theme: Dance & Choreography (dance) · historical dance archives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create immutable NFT records of historical dance footage for rights and preservation. Why Hedera: NFTs anchor archival IPFS content with verifiable creator provenance onchain. Market: TAM $1.5B — dance archival and historic preservation market | SAM $150M — digital archival licensing platforms | SOM $15M — NFT archival holders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PulseProof Archives" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create immutable NFT records of historical dance footage for rights and preservation. Discipline: Dance & Choreography (historical dance archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs anchor archival IPFS content with verifiable creator provenance onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PulseProof Archives" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-spinmint-marketplace-13 Title: SpinMint Marketplace Theme: Dance & Choreography (dance) · 360 dance moves Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Tokenize 360° recorded dance moves as NFTs to share with immersive digital experiences. Why Hedera: NFTs guarantee ownership of complex multimedia dance assets stored on IPFS. Market: TAM $2.8B — immersive dance and digital experience market | SAM $280M — 360° digital dance content licensing | SOM $28M — NFT immersive dance collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SpinMint Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize 360° recorded dance moves as NFTs to share with immersive digital experiences. Discipline: Dance & Choreography (360 dance moves). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs guarantee ownership of complex multimedia dance assets stored on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SpinMint Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movemark-licensing-14 Title: MoveMark Licensing Theme: Dance & Choreography (dance) · choreography licensing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: License choreography sequences securely as NFTs to streamline usage tracking and payments. Why Hedera: NFT provenance ensures transparent onchain tracking of licensing rights and transfers. Market: TAM $4.2B — choreography licensing global market | SAM $420M — digital choreography licensing platforms | SOM $42M — NFT licensing adopters in dance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoveMark Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License choreography sequences securely as NFTs to streamline usage tracking and payments. Discipline: Dance & Choreography (choreography licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures transparent onchain tracking of licensing rights and transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoveMark Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-freezeframe-tokens-15 Title: FreezeFrame Tokens Theme: Dance & Choreography (dance) · iconic frozen poses Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs capturing iconic frozen dance frames for fan engagement and merchandising. Why Hedera: NFTs provide proof of authenticity and scarcity of digitally minted poses. Market: TAM $2.3B — dance fan merchandise and digital collectibles | SAM $230M — frozen pose digital collectibles market | SOM $23M — NFT pose collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FreezeFrame Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs capturing iconic frozen dance frames for fan engagement and merchandising. Discipline: Dance & Choreography (iconic frozen poses). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide proof of authenticity and scarcity of digitally minted poses. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FreezeFrame Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-stepsync-remix-16 Title: StepSync Remix Theme: Dance & Choreography (dance) · dance step sampling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint and license remixable dance steps as NFTs to promote collaborative creation. Why Hedera: NFTs track sample provenance and usage rights transparently onchain. Market: TAM $3.7B — dance sampling and remix culture market | SAM $370M — digital dance sample licensing | SOM $37M — NFT-based step remix users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StepSync Remix" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and license remixable dance steps as NFTs to promote collaborative creation. Discipline: Dance & Choreography (dance step sampling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs track sample provenance and usage rights transparently onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StepSync Remix" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-choreocraft-tokens-17 Title: ChoreoCraft Tokens Theme: Dance & Choreography (dance) · custom choreography kits Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create and sell NFT-backed custom choreography kits for teachers and studios. Why Hedera: NFT minting verifies ownership and uniqueness of choreography kit IP packages. Market: TAM $3B — dance education content creation market | SAM $300M — digital choreography toolkits | SOM $30M — NFT choreography kit buyers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChoreoCraft Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and sell NFT-backed custom choreography kits for teachers and studios. Discipline: Dance & Choreography (custom choreography kits). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting verifies ownership and uniqueness of choreography kit IP packages. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChoreoCraft Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-dancetrail-provenance-18 Title: DanceTrail Provenance Theme: Dance & Choreography (dance) · performance recording Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Record and mint NFT trails of live performances to authenticate originality and authorship. Why Hedera: NFTs link performance data stored on IPFS with immutable onchain proof. Market: TAM $4B — live dance performance recording market | SAM $400M — digital live performance monetization | SOM $40M — NFT authenticated performance users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DanceTrail Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and mint NFT trails of live performances to authenticate originality and authorship. Discipline: Dance & Choreography (performance recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs link performance data stored on IPFS with immutable onchain proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DanceTrail Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-rhythmripple-chain-19 Title: RhythmRipple Chain Theme: Dance & Choreography (dance) · beat choreography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Tokenize rhythm sequences as NFTs for secure ownership and licensing in dance shows. Why Hedera: NFT provenance ensures trusted beat IP management and transfer. Market: TAM $3.2B — dance show music and beat licensing | SAM $320M — digital beat choreography tools | SOM $32M — NFT beat owners and licensors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RhythmRipple Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize rhythm sequences as NFTs for secure ownership and licensing in dance shows. Discipline: Dance & Choreography (beat choreography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures trusted beat IP management and transfer. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RhythmRipple Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-movemint-studio-20 Title: MoveMint Studio Theme: Dance & Choreography (dance) · original move minting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Choreographers mint unique dance moves as NFTs to monetize and protect their creations. Why Hedera: HTS NFT minting enables secure creator ownership and provenance verification. Market: TAM $5B — global choreography creation industry | SAM $500M — digital choreography tools and licensing | SOM $50M — NFT dance move creators and collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoveMint Studio" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Choreographers mint unique dance moves as NFTs to monetize and protect their creations. Discipline: Dance & Choreography (original move minting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting enables secure creator ownership and provenance verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoveMint Studio" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-posechain-gallery-21 Title: PoseChain Gallery Theme: Dance & Choreography (dance) · dance pose exhibitions Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create galleries of NFT-verified dance poses to showcase and sell digital art collections. Why Hedera: NFT provenance ensures uniqueness and rightful ownership onchain. Market: TAM $2B — digital dance art and gallery market | SAM $200M — NFT-based dance art sales | SOM $20M — digital dance art collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoseChain Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create galleries of NFT-verified dance poses to showcase and sell digital art collections. Discipline: Dance & Choreography (dance pose exhibitions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures uniqueness and rightful ownership onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoseChain Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-glideproof-tokens-22 Title: GlideProof Tokens Theme: Dance & Choreography (dance) · fluidity measurement Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs encoding fluidity metrics of dance sequences for analytics and coaching. Why Hedera: NFTs timestamp and verify data stored offchain, linked to creator identities. Market: TAM $1.2B — dance analytics and coaching technology | SAM $120M — digital dance performance data licensing | SOM $12M — NFT-based coaching tool users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "GlideProof Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs encoding fluidity metrics of dance sequences for analytics and coaching. Discipline: Dance & Choreography (fluidity measurement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs timestamp and verify data stored offchain, linked to creator identities. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "GlideProof Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-shadowstep-registry-23 Title: ShadowStep Registry Theme: Dance & Choreography (dance) · silent movement catalog Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Catalog silent or lip-synced dance movements as NFTs for licensing and crediting. Why Hedera: NFT provenance provides clear IP proof for silent choreography assets. Market: TAM $2.7B — dance movement licensing and cataloging | SAM $270M — digital silent dance content market | SOM $27M — NFT silent movement adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ShadowStep Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Catalog silent or lip-synced dance movements as NFTs for licensing and crediting. Discipline: Dance & Choreography (silent movement catalog). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance provides clear IP proof for silent choreography assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ShadowStep Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA dance-leapchain-ledger-24 Title: LeapChain Ledger Theme: Dance & Choreography (dance) · leaps and jumps Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs of signature leaps to prove originality and enable commercial licensing. Why Hedera: NFTs anchor leap moves with immutable creator provenance onchain. Market: TAM $2.5B — dance instructional and rights management | SAM $250M — digital move-specific licensing | SOM $25M — NFT leap move rights holders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LeapChain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs of signature leaps to prove originality and enable commercial licensing. Discipline: Dance & Choreography (leaps and jumps). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs anchor leap moves with immutable creator provenance onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LeapChain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Fashion & Textile Design fashion designers, textile artists, costume designers, stylists ============================================================================== ------------------------------------------------------------------------------ IDEA fashion-fabrictrace-ledger-0 Title: FabricTrace Ledger Theme: Fashion & Textile Design (fashion) · material provenance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and verify organic fabric sources to assure sustainable fashion claims. Why Hedera: Hedera testnet smart contracts ensure immutable, transparent provenance records on-chain. Market: TAM $1.2B — fashion design software market | SAM $250M — sustainable textile tracking tools | SOM $15M — organic fabric brands using blockchain verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FabricTrace Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and verify organic fabric sources to assure sustainable fashion claims. Discipline: Fashion & Textile Design (material provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure immutable, transparent provenance records on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FabricTrace Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-designauth-mint-1 Title: DesignAuth Mint Theme: Fashion & Textile Design (fashion) · design copyright Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely register and timestamp original fashion designs to prevent copying. Why Hedera: Smart contracts provide decentralized, tamper-proof timestamps for design IP. Market: TAM $1.2B — fashion design software market | SAM $180M — design IP protection solutions | SOM $12M — independent designers seeking copyright assurance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DesignAuth Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely register and timestamp original fashion designs to prevent copying. Discipline: Fashion & Textile Design (design copyright). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide decentralized, tamper-proof timestamps for design IP. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DesignAuth Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-colorpalette-dao-2 Title: ColorPalette DAO Theme: Fashion & Textile Design (fashion) · color curation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Collaboratively create and share color palettes with verified ownership and royalty rules. Why Hedera: Hedera testnet contracts enable decentralized governance and royalty distribution. Market: TAM $1.2B — fashion design software market | SAM $100M — digital color tool platforms | SOM $8M — stylists and artists using shared palettes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorPalette DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaboratively create and share color palettes with verified ownership and royalty rules. Discipline: Fashion & Textile Design (color curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable decentralized governance and royalty distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorPalette DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-virtualfitting-nft-3 Title: VirtualFitting NFT Theme: Fashion & Textile Design (fashion) · virtual try-on Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create unique virtual fitting sessions minted as NFTs for secure client access. Why Hedera: Smart contracts manage exclusive session access and ownership transparently. Market: TAM $1.2B — fashion design software market | SAM $200M — virtual fitting room software | SOM $20M — stylists offering exclusive virtual fittings ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VirtualFitting NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create unique virtual fitting sessions minted as NFTs for secure client access. Discipline: Fashion & Textile Design (virtual try-on). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts manage exclusive session access and ownership transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VirtualFitting NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-upcycleproof-chain-4 Title: UpcycleProof Chain Theme: Fashion & Textile Design (fashion) · recycled textile verification Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Authenticate recycled textiles’ origin to validate upcycled fashion products. Why Hedera: Onchain records provide immutable proof of recycling steps and sources. Market: TAM $1.2B — fashion design software market | SAM $150M — upcycled textile verification tools | SOM $10M — brands certifying recycled materials ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "UpcycleProof Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate recycled textiles’ origin to validate upcycled fashion products. Discipline: Fashion & Textile Design (recycled textile verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain records provide immutable proof of recycling steps and sources. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "UpcycleProof Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-trendsignal-oracles-5 Title: TrendSignal Oracles Theme: Fashion & Textile Design (fashion) · trend prediction Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Aggregate verified sales and social data on-chain to predict emerging fashion trends. Why Hedera: Smart contracts automate transparent, tamper-proof data aggregation via oracles. Market: TAM $1.2B — fashion design software market | SAM $120M — trend analytics platforms | SOM $9M — designers leveraging real-time trend insights ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TrendSignal Oracles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Aggregate verified sales and social data on-chain to predict emerging fashion trends. Discipline: Fashion & Textile Design (trend prediction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate transparent, tamper-proof data aggregation via oracles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TrendSignal Oracles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-patternshare-hub-6 Title: PatternShare Hub Theme: Fashion & Textile Design (fashion) · pattern licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: License and trade garment patterns securely using blockchain-based smart contracts. Why Hedera: Hedera testnet contracts manage pattern ownership, licensing, and royalties efficiently. Market: TAM $1.2B — fashion design software market | SAM $130M — pattern sharing marketplaces | SOM $11M — independent pattern makers monetizing work ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PatternShare Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License and trade garment patterns securely using blockchain-based smart contracts. Discipline: Fashion & Textile Design (pattern licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts manage pattern ownership, licensing, and royalties efficiently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PatternShare Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-costumenft-archive-7 Title: CostumeNFT Archive Theme: Fashion & Textile Design (fashion) · historical costume catalog Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create verified digital archives of historical costumes as NFTs for museums and artists. Why Hedera: Smart contracts preserve provenance and authenticity for cultural assets. Market: TAM $1.2B — fashion design software market | SAM $90M — museum digital archive solutions | SOM $7M — costume designers accessing verified archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CostumeNFT Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create verified digital archives of historical costumes as NFTs for museums and artists. Discipline: Fashion & Textile Design (historical costume catalog). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts preserve provenance and authenticity for cultural assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CostumeNFT Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-stylistbooking-chain-8 Title: StylistBooking Chain Theme: Fashion & Textile Design (fashion) · consultation scheduling Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage stylist-client appointments with transparent, automated escrow and approvals. Why Hedera: Smart contracts facilitate secure, trustless booking and payment workflows. Market: TAM $1.2B — fashion design software market | SAM $110M — stylist booking software | SOM $8M — freelance stylists using decentralized bookings ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StylistBooking Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage stylist-client appointments with transparent, automated escrow and approvals. Discipline: Fashion & Textile Design (consultation scheduling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts facilitate secure, trustless booking and payment workflows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StylistBooking Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-sustainlabel-verify-9 Title: SustainLabel Verify Theme: Fashion & Textile Design (fashion) · eco-label validation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Validate sustainable fashion certifications on-chain for trust and transparency. Why Hedera: Immutable contracts prevent label forgery and enable real-time verification. Market: TAM $1.2B — fashion design software market | SAM $140M — eco-label verification platforms | SOM $12M — brands requiring certified transparency ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SustainLabel Verify" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Validate sustainable fashion certifications on-chain for trust and transparency. Discipline: Fashion & Textile Design (eco-label validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable contracts prevent label forgery and enable real-time verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SustainLabel Verify" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-biofiber-token-10 Title: BioFiber Token Theme: Fashion & Textile Design (fashion) · biomaterial crowdfunding Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Tokenize biomaterial innovation projects with secure on-chain investment tracking. Why Hedera: Smart contracts distribute investment tokens and handle transparent fund flows. Market: TAM $1.2B — fashion design software market | SAM $100M — textile innovation crowdfunding | SOM $9M — startups crowdfunding sustainable fibers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "BioFiber Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize biomaterial innovation projects with secure on-chain investment tracking. Discipline: Fashion & Textile Design (biomaterial crowdfunding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts distribute investment tokens and handle transparent fund flows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "BioFiber Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fashioncollab-dao-11 Title: FashionCollab DAO Theme: Fashion & Textile Design (fashion) · collaborative design Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralize fashion design collaboration with smart contract governed decision-making. Why Hedera: Hedera testnet contracts enforce ownership and voting rules in collaborative projects. Market: TAM $1.2B — fashion design software market | SAM $115M — collaborative design tools | SOM $10M — teams using DAO frameworks for fashion ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FashionCollab DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize fashion design collaboration with smart contract governed decision-making. Discipline: Fashion & Textile Design (collaborative design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enforce ownership and voting rules in collaborative projects. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FashionCollab DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-supplychain-mint-12 Title: SupplyChain Mint Theme: Fashion & Textile Design (fashion) · production tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record each production stage on-chain for transparent garment supply chains. Why Hedera: Immutable smart contracts provide verified, tamper-proof production logs. Market: TAM $1.2B — fashion design software market | SAM $160M — fashion supply chain software | SOM $13M — brands enforcing supply transparency ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SupplyChain Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record each production stage on-chain for transparent garment supply chains. Discipline: Fashion & Textile Design (production tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable smart contracts provide verified, tamper-proof production logs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SupplyChain Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-stylelicense-nft-13 Title: StyleLicense NFT Theme: Fashion & Textile Design (fashion) · style licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint and trade exclusive style licenses as NFTs to protect designer creativity. Why Hedera: Smart contracts automate license issuance, transfer, and royalty payments. Market: TAM $1.2B — fashion design software market | SAM $125M — style IP licensing | SOM $10M — designers monetizing unique styles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StyleLicense NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade exclusive style licenses as NFTs to protect designer creativity. Discipline: Fashion & Textile Design (style licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate license issuance, transfer, and royalty payments. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StyleLicense NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabricbatch-chain-14 Title: FabricBatch Chain Theme: Fashion & Textile Design (fashion) · batch quality control Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Log fabric batch quality certifications on-chain for designer trust and compliance. Why Hedera: Smart contracts enable tamper-proof quality certification transparency. Market: TAM $1.2B — fashion design software market | SAM $135M — textile quality management tools | SOM $11M — manufacturers providing batch traceability ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FabricBatch Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Log fabric batch quality certifications on-chain for designer trust and compliance. Discipline: Fashion & Textile Design (batch quality control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable tamper-proof quality certification transparency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FabricBatch Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-wearablestats-token-15 Title: WearableStats Token Theme: Fashion & Textile Design (fashion) · performance textiles Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Tokenize data from smart textiles to reward sustainable usage behaviors. Why Hedera: Onchain tokens enable transparent reward systems linked to textile usage data. Market: TAM $1.2B — fashion design software market | SAM $80M — smart textile software | SOM $6M — brands incentivizing eco-friendly wear ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "WearableStats Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize data from smart textiles to reward sustainable usage behaviors. Discipline: Fashion & Textile Design (performance textiles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokens enable transparent reward systems linked to textile usage data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "WearableStats Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-virtualrunway-dao-16 Title: VirtualRunway DAO Theme: Fashion & Textile Design (fashion) · digital fashion shows Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Host decentralized, verifiable virtual fashion events with transparent gatekeeping. Why Hedera: Smart contracts control access, voting, and payment distribution securely. Market: TAM $1.2B — fashion design software market | SAM $90M — virtual event platforms | SOM $7M — designers producing blockchain-secured shows ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VirtualRunway DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host decentralized, verifiable virtual fashion events with transparent gatekeeping. Discipline: Fashion & Textile Design (digital fashion shows). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts control access, voting, and payment distribution securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VirtualRunway DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-textilewaste-nft-17 Title: TextileWaste NFT Theme: Fashion & Textile Design (fashion) · waste management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue NFTs certifying textile waste recycling to promote circular fashion. Why Hedera: Smart contracts track and authenticate waste recycling steps publicly. Market: TAM $1.2B — fashion design software market | SAM $100M — fashion waste tracking tools | SOM $8M — recyclers certifying textile waste reuse ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TextileWaste NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue NFTs certifying textile waste recycling to promote circular fashion. Discipline: Fashion & Textile Design (waste management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts track and authenticate waste recycling steps publicly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TextileWaste NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-patternproof-chain-18 Title: PatternProof Chain Theme: Fashion & Textile Design (fashion) · pattern authenticity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Verify pattern originality and ownership in decentralized on-chain records. Why Hedera: Immutability secures pattern provenance against unauthorized reuse. Market: TAM $1.2B — fashion design software market | SAM $120M — digital pattern authentication | SOM $9M — designers protecting pattern creations ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PatternProof Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify pattern originality and ownership in decentralized on-chain records. Discipline: Fashion & Textile Design (pattern authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutability secures pattern provenance against unauthorized reuse. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PatternProof Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-accessoryauth-mint-19 Title: AccessoryAuth Mint Theme: Fashion & Textile Design (fashion) · accessory authentication Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue NFTs authenticating limited-edition fashion accessories to combat counterfeits. Why Hedera: Smart contracts provide verifiable ownership and authenticity certificates. Market: TAM $1.2B — fashion design software market | SAM $110M — accessory authentication solutions | SOM $7M — luxury accessory brands using NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AccessoryAuth Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue NFTs authenticating limited-edition fashion accessories to combat counterfeits. Discipline: Fashion & Textile Design (accessory authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide verifiable ownership and authenticity certificates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AccessoryAuth Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fashioncontractor-dao-20 Title: FashionContractor DAO Theme: Fashion & Textile Design (fashion) · freelance contract management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage freelance fashion contracts transparently with on-chain escrow and milestones. Why Hedera: Smart contracts automate payments and enforce contract terms securely. Market: TAM $1.2B — fashion design software market | SAM $95M — freelance contract platforms | SOM $6M — freelance designers using blockchain contracts ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FashionContractor DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage freelance fashion contracts transparently with on-chain escrow and milestones. Discipline: Fashion & Textile Design (freelance contract management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate payments and enforce contract terms securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FashionContractor DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-digitalswatch-token-21 Title: DigitalSwatch Token Theme: Fashion & Textile Design (fashion) · digital fabric samples Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create and trade verified digital fabric swatches with immutable ownership records. Why Hedera: Onchain tokens assure sample authenticity and provenance. Market: TAM $1.2B — fashion design software market | SAM $85M — digital fabric sample tools | SOM $5M — textile artists exchanging digital swatches ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DigitalSwatch Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and trade verified digital fabric swatches with immutable ownership records. Discipline: Fashion & Textile Design (digital fabric samples). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokens assure sample authenticity and provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DigitalSwatch Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-ecofashion-dao-22 Title: EcoFashion DAO Theme: Fashion & Textile Design (fashion) · sustainability governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Govern eco-friendly fashion initiatives via decentralized, transparent smart contracts. Why Hedera: Hedera testnet contracts enable democratic decision-making and fund allocation. Market: TAM $1.2B — fashion design software market | SAM $105M — sustainable fashion governance | SOM $7M — brands engaging community decisions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EcoFashion DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Govern eco-friendly fashion initiatives via decentralized, transparent smart contracts. Discipline: Fashion & Textile Design (sustainability governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable democratic decision-making and fund allocation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EcoFashion DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-runwayroyalties-nft-23 Title: RunwayRoyalties NFT Theme: Fashion & Textile Design (fashion) · event royalty management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate royalty distribution for runway show participants through NFT smart contracts. Why Hedera: Smart contracts transparently distribute earnings per pre-set terms. Market: TAM $1.2B — fashion design software market | SAM $90M — event royalty software | SOM $6M — designers and models receiving automated royalties ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RunwayRoyalties NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate royalty distribution for runway show participants through NFT smart contracts. Discipline: Fashion & Textile Design (event royalty management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts transparently distribute earnings per pre-set terms. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RunwayRoyalties NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-styleswap-chain-24 Title: StyleSwap Chain Theme: Fashion & Textile Design (fashion) · digital fashion exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Trade digital fashion items securely with on-chain ownership and transfer records. Why Hedera: Smart contracts guarantee trustless exchange of digital style assets. Market: TAM $1.2B — fashion design software market | SAM $100M — digital fashion marketplaces | SOM $8M — consumers swapping digital garments ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StyleSwap Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade digital fashion items securely with on-chain ownership and transfer records. Discipline: Fashion & Textile Design (digital fashion exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts guarantee trustless exchange of digital style assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StyleSwap Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-storyline-0 Title: Fabric Storyline Theme: Fashion & Textile Design (fashion) · digital textile archives Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely archive and share original fabric patterns with permanent proof of creativity. Why Hedera: IPFS via Pinata ensures immutable, decentralized storage of design patterns accessible globally. Market: TAM $2B — global digital textile market | SAM $400M — textile design software | SOM $25M — niche digital fabric design tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric Storyline" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely archive and share original fabric patterns with permanent proof of creativity. Discipline: Fashion & Textile Design (digital textile archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata ensures immutable, decentralized storage of design patterns accessible globally. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric Storyline" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-runway-replay-1 Title: Runway Replay Theme: Fashion & Textile Design (fashion) · fashion show documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin high-resolution show images and metadata to create an unalterable runway history. Why Hedera: Pinata guarantees permanent, tamper-proof storage for fashion event documentation. Market: TAM $1.5B — global fashion event content market | SAM $250M — fashion show digital assets | SOM $15M — event archiving solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Runway Replay" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin high-resolution show images and metadata to create an unalterable runway history. Discipline: Fashion & Textile Design (fashion show documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata guarantees permanent, tamper-proof storage for fashion event documentation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Runway Replay" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-color-code-vault-2 Title: Color Code Vault Theme: Fashion & Textile Design (fashion) · color palette preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store confirmed color palettes and their usage securely for design authenticity. Why Hedera: Pinata's immutable storage validates and preserves exact color codes over time. Market: TAM $500M — global color management software | SAM $120M — fashion color tools | SOM $10M — color palette storage apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Color Code Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store confirmed color palettes and their usage securely for design authenticity. Discipline: Fashion & Textile Design (color palette preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's immutable storage validates and preserves exact color codes over time. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Color Code Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-stitch-trace-3 Title: Stitch Trace Theme: Fashion & Textile Design (fashion) · garment construction logs Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Record detailed stitch patterns and techniques on-chain to prove craftsmanship. Why Hedera: Decentralized, permanent IPFS pins prevent tampering with design process records. Market: TAM $600M — global fashion manufacturing software | SAM $130M — textile production tracking | SOM $12M — artisan stitch documentation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Stitch Trace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record detailed stitch patterns and techniques on-chain to prove craftsmanship. Discipline: Fashion & Textile Design (garment construction logs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Decentralized, permanent IPFS pins prevent tampering with design process records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Stitch Trace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-moodboard-ledger-4 Title: Moodboard Ledger Theme: Fashion & Textile Design (fashion) · inspiration curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin moodboards with images and notes securely to track creative evolution. Why Hedera: Pinata provides permanent storage for evolving, multi-format design inspirations. Market: TAM $700M — digital creative asset management | SAM $150M — fashion moodboard tools | SOM $11M — pinned inspiration apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Moodboard Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin moodboards with images and notes securely to track creative evolution. Discipline: Fashion & Textile Design (inspiration curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata provides permanent storage for evolving, multi-format design inspirations. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Moodboard Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-trend-lineage-5 Title: Trend Lineage Theme: Fashion & Textile Design (fashion) · fashion trend mapping Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Archive trend data and visuals to verify origin and evolution of styles. Why Hedera: IPFS pins maintain unchangeable records of trend sources and timelines. Market: TAM $1B — fashion analytics market | SAM $200M — trend forecasting software | SOM $18M — trend provenance tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Trend Lineage" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive trend data and visuals to verify origin and evolution of styles. Discipline: Fashion & Textile Design (fashion trend mapping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins maintain unchangeable records of trend sources and timelines. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Trend Lineage" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-texture-token-6 Title: Texture Token Theme: Fashion & Textile Design (fashion) · fabric texture archives Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin high-res texture samples for sharing and verifying textile uniqueness. Why Hedera: Pinata ensures permanent, decentralized storage of texture image data. Market: TAM $800M — global textile sample market | SAM $180M — fabric library platforms | SOM $14M — texture sharing tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Texture Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin high-res texture samples for sharing and verifying textile uniqueness. Discipline: Fashion & Textile Design (fabric texture archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures permanent, decentralized storage of texture image data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Texture Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fit-file-safe-7 Title: Fit File Safe Theme: Fashion & Textile Design (fashion) · 3D garment fitting Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store 3D fitting session data securely for consistent tailoring and reuse. Why Hedera: IPFS pins preserve complex 3D fitting files immutably offchain. Market: TAM $1.2B — global 3D fashion tech | SAM $300M — virtual fitting software | SOM $22M — fit data storage platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fit File Safe" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store 3D fitting session data securely for consistent tailoring and reuse. Discipline: Fashion & Textile Design (3D garment fitting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins preserve complex 3D fitting files immutably offchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fit File Safe" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-accessory-archive-8 Title: Accessory Archive Theme: Fashion & Textile Design (fashion) · digital accessory cataloging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin detailed accessory designs for easy access and IP validation. Why Hedera: Pinata enables permanent, tamper-resistant archival of accessory assets. Market: TAM $500M — accessory design market | SAM $110M — accessory software tools | SOM $8M — accessory IP management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Accessory Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin detailed accessory designs for easy access and IP validation. Discipline: Fashion & Textile Design (digital accessory cataloging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables permanent, tamper-resistant archival of accessory assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Accessory Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-capsule-catalog-9 Title: Capsule Catalog Theme: Fashion & Textile Design (fashion) · seasonal collection storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store entire seasonal lookbooks as immutable IPFS manifests for authenticity. Why Hedera: Pinata ensures permanent, verifiable storage of complex collection data. Market: TAM $1.8B — global fashion catalog market | SAM $350M — collection management software | SOM $20M — seasonal archive solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Capsule Catalog" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store entire seasonal lookbooks as immutable IPFS manifests for authenticity. Discipline: Fashion & Textile Design (seasonal collection storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures permanent, verifiable storage of complex collection data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Capsule Catalog" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-drape-documentation-10 Title: Drape Documentation Theme: Fashion & Textile Design (fashion) · fabric draping records Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin high-res photos and notes on draping experiments for design validation. Why Hedera: IPFS pins preserve ephemeral draping visuals immutably and accessibly. Market: TAM $600M — fashion design experimentation | SAM $130M — fabric manipulation tools | SOM $9M — draping documentation apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Drape Documentation" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin high-res photos and notes on draping experiments for design validation. Discipline: Fashion & Textile Design (fabric draping records). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins preserve ephemeral draping visuals immutably and accessibly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Drape Documentation" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-print-provenance-11 Title: Print Provenance Theme: Fashion & Textile Design (fashion) · digital print design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely pin original print artworks to prevent unauthorized reproduction. Why Hedera: Pinata provides immutable proofs of print artwork originality on IPFS. Market: TAM $900M — digital textile print market | SAM $210M — print design software | SOM $16M — print IP protection ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Print Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely pin original print artworks to prevent unauthorized reproduction. Discipline: Fashion & Textile Design (digital print design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata provides immutable proofs of print artwork originality on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Print Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-ecofabric-ledger-12 Title: EcoFabric Ledger Theme: Fashion & Textile Design (fashion) · sustainable textile tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Archive sustainability data and certifications for eco-friendly fabrics. Why Hedera: Pinata ensures permanent, verifiable sustainability information storage. Market: TAM $1.1B — sustainable fashion market | SAM $280M — eco textile tracking | SOM $19M — sustainability certification archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EcoFabric Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive sustainability data and certifications for eco-friendly fabrics. Discipline: Fashion & Textile Design (sustainable textile tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures permanent, verifiable sustainability information storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EcoFabric Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-pattern-pathway-13 Title: Pattern Pathway Theme: Fashion & Textile Design (fashion) · seamless pattern sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin editable pattern files for seamless transfer and secure versioning. Why Hedera: IPFS guarantees immutable and accessible sharing of pattern files. Market: TAM $1.4B — pattern design software | SAM $320M — pattern sharing platforms | SOM $23M — pattern version control apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pattern Pathway" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin editable pattern files for seamless transfer and secure versioning. Discipline: Fashion & Textile Design (seamless pattern sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees immutable and accessible sharing of pattern files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pattern Pathway" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-avant-atlas-14 Title: Avant Atlas Theme: Fashion & Textile Design (fashion) · experimental design archives Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store experimental fashion concepts securely to document avant-garde evolution. Why Hedera: Pinata's permanent storage preserves niche design experiments transparently. Market: TAM $400M — avant-garde fashion market | SAM $90M — experimental design tools | SOM $7M — conceptual fashion archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Avant Atlas" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store experimental fashion concepts securely to document avant-garde evolution. Discipline: Fashion & Textile Design (experimental design archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's permanent storage preserves niche design experiments transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Avant Atlas" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-fusion-15 Title: Fabric Fusion Theme: Fashion & Textile Design (fashion) · mixed media textile records Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin multimedia fabric experiments combining textiles and digital art. Why Hedera: IPFS via Pinata supports diverse file formats for mixed media preservation. Market: TAM $700M — digital textile innovation | SAM $150M — multimedia fabric software | SOM $10M — mixed media fashion archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric Fusion" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin multimedia fabric experiments combining textiles and digital art. Discipline: Fashion & Textile Design (mixed media textile records). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata supports diverse file formats for mixed media preservation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric Fusion" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-wearable-wallet-16 Title: Wearable Wallet Theme: Fashion & Textile Design (fashion) · digital wardrobe management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin detailed digital wardrobe inventories with immutable metadata. Why Hedera: Pinata ensures permanent availability and proof of digital clothing assets. Market: TAM $1B — digital wardrobe apps | SAM $250M — wardrobe management software | SOM $17M — digital closet archiving ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Wearable Wallet" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin detailed digital wardrobe inventories with immutable metadata. Discipline: Fashion & Textile Design (digital wardrobe management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures permanent availability and proof of digital clothing assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Wearable Wallet" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-costume-chronicle-17 Title: Costume Chronicle Theme: Fashion & Textile Design (fashion) · theatrical costume records Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely archive costume design images and details for production history. Why Hedera: Pinata pins ensure durable, tamper-proof costume archives. Market: TAM $600M — costume design market | SAM $140M — costume documentation software | SOM $11M — theatrical costume archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Costume Chronicle" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely archive costume design images and details for production history. Discipline: Fashion & Textile Design (theatrical costume records). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins ensure durable, tamper-proof costume archives. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Costume Chronicle" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-style-snapshot-18 Title: Style Snapshot Theme: Fashion & Textile Design (fashion) · fashion influencer galleries Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin curated influencer style images and metadata for trend analysis. Why Hedera: IPFS via Pinata secures permanent influencer content storage. Market: TAM $1.2B — influencer marketing fashion | SAM $280M — influencer content management | SOM $21M — influencer style archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Snapshot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin curated influencer style images and metadata for trend analysis. Discipline: Fashion & Textile Design (fashion influencer galleries). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata secures permanent influencer content storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Snapshot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-thread-token-19 Title: Thread Token Theme: Fashion & Textile Design (fashion) · yarn and thread digital catalogs Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin yarn samples and thread color specs for easy supplier access. Why Hedera: Pinata’s permanent storage protects detailed yarn metadata and images. Market: TAM $700M — yarn and thread supply chain | SAM $160M — textile supplier software | SOM $12M — digital thread catalogues ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Thread Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin yarn samples and thread color specs for easy supplier access. Discipline: Fashion & Textile Design (yarn and thread digital catalogs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s permanent storage protects detailed yarn metadata and images. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Thread Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fashion-footprint-20 Title: Fashion Footprint Theme: Fashion & Textile Design (fashion) · design lifecycle tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin comprehensive lifecycle data of designs for transparency and accountability. Why Hedera: Pinata ensures immutable archival of design production and usage data. Market: TAM $1.5B — fashion supply chain analytics | SAM $300M — lifecycle tracking tools | SOM $18M — accountability archive apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fashion Footprint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin comprehensive lifecycle data of designs for transparency and accountability. Discipline: Fashion & Textile Design (design lifecycle tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures immutable archival of design production and usage data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fashion Footprint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-pattern-nft-forge-21 Title: Pattern NFT Forge Theme: Fashion & Textile Design (fashion) · blockchain pattern minting Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin pattern files to IPFS to mint unique, verifiable design NFTs. Why Hedera: Pinata's IPFS integration enables secure permanent storage for NFT minting. Market: TAM $500M — fashion NFT market | SAM $130M — design minting platforms | SOM $9M — pattern NFT tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pattern NFT Forge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin pattern files to IPFS to mint unique, verifiable design NFTs. Discipline: Fashion & Textile Design (blockchain pattern minting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's IPFS integration enables secure permanent storage for NFT minting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pattern NFT Forge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-digital-drapery-hub-22 Title: Digital Drapery Hub Theme: Fashion & Textile Design (fashion) · online fabric simulation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin fabric simulation data and images for shared digital draping experiences. Why Hedera: IPFS pins preserve large simulation files with permanent access. Market: TAM $900M — fabric simulation software | SAM $220M — collaborative design tools | SOM $14M — fabric simulation archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Digital Drapery Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin fabric simulation data and images for shared digital draping experiences. Discipline: Fashion & Textile Design (online fabric simulation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins preserve large simulation files with permanent access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Digital Drapery Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-ethical-edit-log-23 Title: Ethical Edit Log Theme: Fashion & Textile Design (fashion) · design decision auditing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin timestamps and notes on ethical design choices for industry trust. Why Hedera: Pinata provides immutable proof of ethical design documentation. Market: TAM $800M — ethical fashion software | SAM $180M — transparency tools | SOM $13M — ethical audit archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Ethical Edit Log" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin timestamps and notes on ethical design choices for industry trust. Discipline: Fashion & Textile Design (design decision auditing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata provides immutable proof of ethical design documentation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Ethical Edit Log" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-silhouette-sync-24 Title: Silhouette Sync Theme: Fashion & Textile Design (fashion) · design silhouette sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin silhouette sketches and metadata for collaborative design iterations. Why Hedera: Pinata allows permanent, accessible storage for evolving design sketches. Market: TAM $1.1B — fashion collaboration tools | SAM $270M — silhouette design software | SOM $16M — sketch archiving platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Silhouette Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin silhouette sketches and metadata for collaborative design iterations. Discipline: Fashion & Textile Design (design silhouette sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata allows permanent, accessible storage for evolving design sketches. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Silhouette Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-storychain-0 Title: Fabric Storychain Theme: Fashion & Textile Design (fashion) · material provenance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track textile origins transparently for designers and consumers to verify authenticity. Why Hedera: Magic Link email sign-in enables seamless user authentication and gasless tracing of textile provenance. Market: TAM $1.2B — global fashion design software market | SAM $200M — textile traceability software segment | SOM $20M — early adopters in sustainable fashion brands ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric Storychain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track textile origins transparently for designers and consumers to verify authenticity. Discipline: Fashion & Textile Design (material provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in enables seamless user authentication and gasless tracing of textile provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric Storychain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-moodboard-mint-1 Title: Moodboard Mint Theme: Fashion & Textile Design (fashion) · collaborative curation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and share moodboards that capture design inspirations with onchain credits and permissions. Why Hedera: Magic Link email sign-in allows easy Google sign-in and Hedera's fixed sub-cent fees to enable frictionless moodboard sharing. Market: TAM $1.2B — overall fashion design software market | SAM $150M — collaborative design tools | SOM $15M — social curation platforms for fashion ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Moodboard Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and share moodboards that capture design inspirations with onchain credits and permissions. Discipline: Fashion & Textile Design (collaborative curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in allows easy Google sign-in and Hedera's fixed sub-cent fees to enable frictionless moodboard sharing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Moodboard Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-trendtoken-vault-2 Title: TrendToken Vault Theme: Fashion & Textile Design (fashion) · trend validation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collect and verify emerging fashion trends via community consensus and rewarded participation. Why Hedera: Hedera's fixed sub-cent fees enable users to vote on trends without gas fees, increasing engagement. Market: TAM $1.2B — global fashion design software | SAM $100M — trend analytics tools | SOM $10M — blockchain-based verification solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TrendToken Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collect and verify emerging fashion trends via community consensus and rewarded participation. Discipline: Fashion & Textile Design (trend validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees enable users to vote on trends without gas fees, increasing engagement. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TrendToken Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-patternshare-3 Title: PatternShare Theme: Fashion & Textile Design (fashion) · digital pattern exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely share and license digital sewing patterns with transparent ownership and usage rights. Why Hedera: Magic Link email sign-in bootstrapped by PRIVY_APP_ID ensures seamless user access and transaction sponsorship. Market: TAM $1.2B — fashion design software | SAM $180M — pattern design and licensing | SOM $18M — blockchain licensing for textiles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PatternShare" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely share and license digital sewing patterns with transparent ownership and usage rights. Discipline: Fashion & Textile Design (digital pattern exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in bootstrapped by PRIVY_APP_ID ensures seamless user access and transaction sponsorship. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PatternShare" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-colorchain-palette-4 Title: ColorChain Palette Theme: Fashion & Textile Design (fashion) · color provenance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Verify and share color formula origins to protect designer intellectual property. Why Hedera: Google sign-in and gasless transactions simplify tracking color data ownership onchain. Market: TAM $1.2B — fashion design software | SAM $120M — color management systems | SOM $12M — secure color formula sharing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorChain Palette" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify and share color formula origins to protect designer intellectual property. Discipline: Fashion & Textile Design (color provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Google sign-in and gasless transactions simplify tracking color data ownership onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorChain Palette" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-wearable-royalties-5 Title: Wearable Royalties Theme: Fashion & Textile Design (fashion) · digital fashion royalties Hedera hook: Magic Link email wallet [wallet UX] Pitch: Automatically distribute royalties for digital fashion assets across creators and brands. Why Hedera: Hedera's fixed sub-cent fees facilitate gas-free royalty payments triggered by user activity. Market: TAM $1.2B — fashion design software | SAM $300M — digital fashion monetization | SOM $30M — blockchain royalty platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Wearable Royalties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically distribute royalties for digital fashion assets across creators and brands. Discipline: Fashion & Textile Design (digital fashion royalties). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees facilitate gas-free royalty payments triggered by user activity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Wearable Royalties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-runway-replay-6 Title: Runway Replay Theme: Fashion & Textile Design (fashion) · event recording Hedera hook: Magic Link email wallet [wallet UX] Pitch: Capture and timestamp runway shows for authenticity and replay rights management. Why Hedera: Magic Link email sign-in login ensures easy user onboarding and timestamping without gas friction. Market: TAM $800M — fashion event tech market | SAM $100M — event content rights management | SOM $8M — blockchain-backed show archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Runway Replay" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Capture and timestamp runway shows for authenticity and replay rights management. Discipline: Fashion & Textile Design (event recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in login ensures easy user onboarding and timestamping without gas friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Runway Replay" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-sartorial-social-7 Title: Sartorial Social Theme: Fashion & Textile Design (fashion) · designer networking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Connect fashion creatives with gasless transactions enabling easy contract signing and collaborations. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees ensures smooth social interaction without gas barrier. Market: TAM $500M — fashion networking software | SAM $75M — digital collaboration tools | SOM $7M — blockchain contract solutions for creatives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sartorial Social" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Connect fashion creatives with gasless transactions enabling easy contract signing and collaborations. Discipline: Fashion & Textile Design (designer networking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees ensures smooth social interaction without gas barrier. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sartorial Social" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-fraction-8 Title: Fabric Fraction Theme: Fashion & Textile Design (fashion) · material micro-ownership Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable fractional ownership of rare textile swatches through secure tokenization and seamless onboarding. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees minimizes user friction in fractional ownership. Market: TAM $1.2B — fashion design software | SAM $90M — textile asset tokenization | SOM $9M — early adoption in luxury textiles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric Fraction" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable fractional ownership of rare textile swatches through secure tokenization and seamless onboarding. Discipline: Fashion & Textile Design (material micro-ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees minimizes user friction in fractional ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric Fraction" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-virtual-trychain-9 Title: Virtual Trychain Theme: Fashion & Textile Design (fashion) · try-on verification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Authenticate virtual try-on sessions and purchases transparently for customers and brands. Why Hedera: Onchain recording with gasless transactions prevents user drop-off during authentication. Market: TAM $1.2B — fashion software market | SAM $250M — virtual fitting solutions | SOM $25M — blockchain try-on authentication ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Trychain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate virtual try-on sessions and purchases transparently for customers and brands. Discipline: Fashion & Textile Design (try-on verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain recording with gasless transactions prevents user drop-off during authentication. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Trychain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-accessory-auth-10 Title: Accessory Auth Theme: Fashion & Textile Design (fashion) · jewelry provenance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Provide verified origin records for fashion accessories and fine jewelry owners. Why Hedera: Magic Link email sign-in login and Hedera's fixed sub-cent fees enable secure provenance recording and easy user access. Market: TAM $700M — accessory design software | SAM $90M — provenance verification | SOM $9M — blockchain provenance for jewelry ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Accessory Auth" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Provide verified origin records for fashion accessories and fine jewelry owners. Discipline: Fashion & Textile Design (jewelry provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in login and Hedera's fixed sub-cent fees enable secure provenance recording and easy user access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Accessory Auth" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-capsule-contract-11 Title: Capsule Contract Theme: Fashion & Textile Design (fashion) · collaborative capsule collections Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable designers to co-create and manage capsule collections with frictionless onchain agreements. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplifies contract setup and management without gas costs. Market: TAM $1.2B — fashion design software | SAM $130M — collaboration management | SOM $13M — blockchain contracts for fashion ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Capsule Contract" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable designers to co-create and manage capsule collections with frictionless onchain agreements. Discipline: Fashion & Textile Design (collaborative capsule collections). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplifies contract setup and management without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Capsule Contract" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-style-stake-12 Title: Style Stake Theme: Fashion & Textile Design (fashion) · community style curation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Stake tokens to support favorite designers and earn rewards through community votes. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees enables easy staking and reward distribution without user gas burden. Market: TAM $1.2B — fashion software market | SAM $140M — community curation platforms | SOM $14M — blockchain staking in fashion ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Stake" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Stake tokens to support favorite designers and earn rewards through community votes. Discipline: Fashion & Textile Design (community style curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees enables easy staking and reward distribution without user gas burden. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Stake" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-swapchain-13 Title: Fabric Swapchain Theme: Fashion & Textile Design (fashion) · material bartering Hedera hook: Magic Link email wallet [wallet UX] Pitch: Decentralize textile swaps between designers via secure tokenized exchanges. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees facilitate seamless trades without gas friction. Market: TAM $1.2B — fashion design software | SAM $110M — material exchange platforms | SOM $11M — blockchain-based swaps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric Swapchain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize textile swaps between designers via secure tokenized exchanges. Discipline: Fashion & Textile Design (material bartering). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees facilitate seamless trades without gas friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric Swapchain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-runway-reward-14 Title: Runway Reward Theme: Fashion & Textile Design (fashion) · event incentives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward attendance and participation at fashion events with tokenized perks and proof of presence. Why Hedera: Gasless tx encourages user engagement without friction during event check-ins. Market: TAM $800M — fashion event tech | SAM $60M — event incentive platforms | SOM $6M — blockchain rewards for attendees ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Runway Reward" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward attendance and participation at fashion events with tokenized perks and proof of presence. Discipline: Fashion & Textile Design (event incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Gasless tx encourages user engagement without friction during event check-ins. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Runway Reward" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-style-snapshot-15 Title: Style Snapshot Theme: Fashion & Textile Design (fashion) · digital outfit logging Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely log daily outfits as NFTs with easy social sign-in and no gas cost. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees let users mint outfit NFTs without blockchain experience. Market: TAM $1.2B — fashion design software | SAM $90M — digital wardrobe management | SOM $9M — blockchain outfit logs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Snapshot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely log daily outfits as NFTs with easy social sign-in and no gas cost. Discipline: Fashion & Textile Design (digital outfit logging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees let users mint outfit NFTs without blockchain experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Snapshot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-craft-credit-16 Title: Craft Credit Theme: Fashion & Textile Design (fashion) · artisan attribution Hedera hook: Magic Link email wallet [wallet UX] Pitch: Automatically credit textile artisans in design chains with transparent onchain records. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees automate attribution without user gas concerns. Market: TAM $1.2B — fashion software | SAM $80M — artisan tracking tools | SOM $8M — onchain artisan credit solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Craft Credit" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically credit textile artisans in design chains with transparent onchain records. Discipline: Fashion & Textile Design (artisan attribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees automate attribution without user gas concerns. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Craft Credit" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-pattern-provenance-17 Title: Pattern Provenance Theme: Fashion & Textile Design (fashion) · design copyright Hedera hook: Magic Link email wallet [wallet UX] Pitch: Protect sewing patterns with immutable copyright records accessible via Google login. Why Hedera: Gasless Hedera's fixed sub-cent fees enable pattern creators to register rights affordably and easily. Market: TAM $1.2B — fashion design software | SAM $100M — copyright management | SOM $10M — blockchain copyright for patterns ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pattern Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Protect sewing patterns with immutable copyright records accessible via Google login. Discipline: Fashion & Textile Design (design copyright). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Gasless Hedera's fixed sub-cent fees enable pattern creators to register rights affordably and easily. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pattern Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-tailor-token-18 Title: Tailor Token Theme: Fashion & Textile Design (fashion) · custom order management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable tailors to accept and manage custom orders via tokenized contracts and gasless sign-in. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees reduces friction for small business adoption. Market: TAM $500M — bespoke tailoring software | SAM $70M — custom order platforms | SOM $7M — blockchain contract management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tailor Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable tailors to accept and manage custom orders via tokenized contracts and gasless sign-in. Discipline: Fashion & Textile Design (custom order management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees reduces friction for small business adoption. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tailor Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fashion-dao-hub-19 Title: Fashion DAO Hub Theme: Fashion & Textile Design (fashion) · decentralized fashion governance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Empower designer collectives to govern projects through token voting with easy wallet setup. Why Hedera: Magic Link email sign-in plus gasless transactions lower barriers for DAO participation in fashion niches. Market: TAM $1.2B — fashion design software | SAM $160M — community governance tools | SOM $16M — blockchain DAOs for designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fashion DAO Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Empower designer collectives to govern projects through token voting with easy wallet setup. Discipline: Fashion & Textile Design (decentralized fashion governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus gasless transactions lower barriers for DAO participation in fashion niches. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fashion DAO Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-vintage-vault-20 Title: Vintage Vault Theme: Fashion & Textile Design (fashion) · authenticity verification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Authenticate vintage fashion pieces via onchain certificates linked to verified user profiles. Why Hedera: the embedded wallet Google sign-in and Hedera's fixed sub-cent fees enable user-friendly provenance verification. Market: TAM $700M — vintage fashion market software | SAM $75M — authenticity platforms | SOM $7.5M — blockchain vintage authentication ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Vintage Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate vintage fashion pieces via onchain certificates linked to verified user profiles. Discipline: Fashion & Textile Design (authenticity verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Google sign-in and Hedera's fixed sub-cent fees enable user-friendly provenance verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Vintage Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-style-swap-21 Title: Style Swap Theme: Fashion & Textile Design (fashion) · peer-to-peer fashion exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Facilitate safe swaps of clothing items with secure tokenized agreements and zero gas fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees encourage user trades by removing blockchain friction. Market: TAM $1.2B — fashion design software | SAM $120M — peer fashion exchange | SOM $12M — blockchain-based swap platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate safe swaps of clothing items with secure tokenized agreements and zero gas fees. Discipline: Fashion & Textile Design (peer-to-peer fashion exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees encourage user trades by removing blockchain friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-textile-tokenizer-22 Title: Textile Tokenizer Theme: Fashion & Textile Design (fashion) · fabric asset digitization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Convert physical textile assets into digital tokens for trading and provenance tracking. Why Hedera: Magic Link email sign-in simplifies token creation and Hedera's fixed sub-cent fees removes cost barriers. Market: TAM $1.2B — fashion software market | SAM $140M — textile digitization | SOM $14M — tokenized asset trading ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Textile Tokenizer" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Convert physical textile assets into digital tokens for trading and provenance tracking. Discipline: Fashion & Textile Design (fabric asset digitization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in simplifies token creation and Hedera's fixed sub-cent fees removes cost barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Textile Tokenizer" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-designer-direct-23 Title: Designer Direct Theme: Fashion & Textile Design (fashion) · supply chain transparency Hedera hook: Magic Link email wallet [wallet UX] Pitch: Connect designers directly with suppliers using onchain verified transactions and profiles. Why Hedera: Magic Link email sign-in login and gasless tx simplify supply chain verification and reduce intermediaries. Market: TAM $1.2B — fashion design software | SAM $130M — supply chain platforms | SOM $13M — blockchain transparency solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Designer Direct" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Connect designers directly with suppliers using onchain verified transactions and profiles. Discipline: Fashion & Textile Design (supply chain transparency). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in login and gasless tx simplify supply chain verification and reduce intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Designer Direct" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-capsule-claim-24 Title: Capsule Claim Theme: Fashion & Textile Design (fashion) · limited drop authentication Hedera hook: Magic Link email wallet [wallet UX] Pitch: Issue onchain certificates for limited edition capsule collections ensuring buyer confidence. Why Hedera: Google sign-in with Hedera's fixed sub-cent fees allows secure certificate minting without gas hurdles. Market: TAM $1.2B — fashion design software | SAM $110M — limited edition verification | SOM $11M — blockchain certificates for drops ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Capsule Claim" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue onchain certificates for limited edition capsule collections ensuring buyer confidence. Discipline: Fashion & Textile Design (limited drop authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Google sign-in with Hedera's fixed sub-cent fees allows secure certificate minting without gas hurdles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Capsule Claim" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-thread-legacy-0 Title: Thread Legacy Theme: Fashion & Textile Design (fashion) · fabric provenance Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate fabric origins transparently for sustainable fashion designers. Why Hedera: NFT provenance mint ensures immutable proof of fabric source and authenticity. Market: TAM $300M — global sustainable fabric traceability market | SAM $80M — fashion designers focused on eco-friendly sourcing | SOM $5M — early adopters in ethical fabric provenance apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Thread Legacy" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate fabric origins transparently for sustainable fashion designers. Discipline: Fashion & Textile Design (fabric provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mint ensures immutable proof of fabric source and authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Thread Legacy" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-costume-chronicle-1 Title: Costume Chronicle Theme: Fashion & Textile Design (fashion) · historical costume design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and showcase original costume designs as unique digital collectibles. Why Hedera: HTS NFT tokens uniquely represent each costume's verified digital identity. Market: TAM $120M — costume design software market | SAM $35M — theatrical and film costume designers | SOM $3M — NFT-based costume design adoption in entertainment ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Costume Chronicle" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and showcase original costume designs as unique digital collectibles. Discipline: Fashion & Textile Design (historical costume design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens uniquely represent each costume's verified digital identity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Costume Chronicle" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-style-vault-2 Title: Style Vault Theme: Fashion & Textile Design (fashion) · outfit curation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create exclusive NFT collections of curated outfits for personalized style portfolios. Why Hedera: Minting NFTs onchain proves ownership and originality of curated digital styles. Market: TAM $500M — fashion curation platforms worldwide | SAM $150M — personal stylists and fashion consultants | SOM $10M — NFT adoption among digital fashion curators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create exclusive NFT collections of curated outfits for personalized style portfolios. Discipline: Fashion & Textile Design (outfit curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Minting NFTs onchain proves ownership and originality of curated digital styles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-pattern-provenance-3 Title: Pattern Provenance Theme: Fashion & Textile Design (fashion) · textile pattern design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Record and verify original textile patterns as immutable digital assets. Why Hedera: NFT minting binds patterns to creators and timestamps on IPFS metadata. Market: TAM $250M — textile design software market | SAM $70M — freelance textile and surface designers | SOM $4M — NFT-based pattern registration use cases ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pattern Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and verify original textile patterns as immutable digital assets. Discipline: Fashion & Textile Design (textile pattern design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting binds patterns to creators and timestamps on IPFS metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pattern Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-colorcode-ledger-4 Title: ColorCode Ledger Theme: Fashion & Textile Design (fashion) · color study Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate unique color palettes as exclusive NFTs for designers' intellectual property. Why Hedera: Onchain minting secures color palette uniqueness and ownership transparently. Market: TAM $100M — color management tools in fashion | SAM $30M — apparel and accessory color designers | SOM $2M — early color palette NFT adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorCode Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate unique color palettes as exclusive NFTs for designers' intellectual property. Discipline: Fashion & Textile Design (color study). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain minting secures color palette uniqueness and ownership transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorCode Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-nft-atlas-5 Title: Fabric NFT Atlas Theme: Fashion & Textile Design (fashion) · textile sourcing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Map and mint provenance NFTs for regional and artisanal fabric producers. Why Hedera: HTS NFT tokens establish verifiable origin proof for fabric supply chains. Market: TAM $400M — global textile sourcing market | SAM $120M — fashion brands sourcing artisanal fabrics | SOM $8M — provenance NFT use in artisanal textiles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric NFT Atlas" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Map and mint provenance NFTs for regional and artisanal fabric producers. Discipline: Fashion & Textile Design (textile sourcing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens establish verifiable origin proof for fabric supply chains. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric NFT Atlas" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-design-chain-diary-6 Title: Design Chain Diary Theme: Fashion & Textile Design (fashion) · fashion sketching Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Chronologically mint fashion sketches as NFTs to prove creative progress and ownership. Why Hedera: Immutable onchain records timestamp and protect design evolution securely. Market: TAM $350M — fashion design software users | SAM $90M — independent fashion illustrators | SOM $6M — NFT adoption by sketch-based designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Design Chain Diary" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Chronologically mint fashion sketches as NFTs to prove creative progress and ownership. Discipline: Fashion & Textile Design (fashion sketching). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable onchain records timestamp and protect design evolution securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Design Chain Diary" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-capsule-mint-7 Title: Capsule Mint Theme: Fashion & Textile Design (fashion) · collection drops Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Issue limited-edition fashion collection NFTs with provenance for pre-sale exclusivity. Why Hedera: HTS NFT tokens provide verifiable scarcity and authenticity for digital fashion drops. Market: TAM $600M — limited-edition fashion market | SAM $200M — fashion brands launching exclusive capsule collections | SOM $15M — NFT-driven limited drop platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Capsule Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue limited-edition fashion collection NFTs with provenance for pre-sale exclusivity. Discipline: Fashion & Textile Design (collection drops). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide verifiable scarcity and authenticity for digital fashion drops. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Capsule Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-weave-witness-8 Title: Weave Witness Theme: Fashion & Textile Design (fashion) · handloom textiles Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs to certify handcrafted fabric authenticity and artisan identity. Why Hedera: NFTs serve as tamper-proof certificates linking artisans to their handwoven products. Market: TAM $180M — handloom textile market globally | SAM $50M — designers focused on handcrafted textiles | SOM $3M — provenance NFT use in handloom fabrics ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Weave Witness" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs to certify handcrafted fabric authenticity and artisan identity. Discipline: Fashion & Textile Design (handloom textiles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs serve as tamper-proof certificates linking artisans to their handwoven products. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Weave Witness" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-trend-tokenizer-9 Title: Trend Tokenizer Theme: Fashion & Textile Design (fashion) · fashion forecasting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint data-driven trend insights as NFTs to securely sell forecasting reports. Why Hedera: NFT provenance ensures originality and ownership of proprietary trend data. Market: TAM $220M — global fashion forecasting services | SAM $70M — fashion analytics and consultancy firms | SOM $4M — NFT adoption in trend insight monetization ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Trend Tokenizer" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint data-driven trend insights as NFTs to securely sell forecasting reports. Discipline: Fashion & Textile Design (fashion forecasting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures originality and ownership of proprietary trend data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Trend Tokenizer" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fashion-dna-10 Title: Fashion DNA Theme: Fashion & Textile Design (fashion) · brand identity Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create unique NFTs representing brand values and design DNA to protect brand authenticity. Why Hedera: HTS NFT minting immutably encodes brand provenance onchain and on IPFS. Market: TAM $800M — brand management software market | SAM $250M — emerging fashion brands protecting IP | SOM $20M — NFT-based brand authenticity initiatives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fashion DNA" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create unique NFTs representing brand values and design DNA to protect brand authenticity. Discipline: Fashion & Textile Design (brand identity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting immutably encodes brand provenance onchain and on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fashion DNA" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-upcycle-proof-11 Title: Upcycle Proof Theme: Fashion & Textile Design (fashion) · sustainable design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs for upcycled garment transformations as proof of sustainable creative ownership. Why Hedera: Onchain tokens give verifiable provenance for altered or recycled fashion pieces. Market: TAM $150M — sustainable fashion upcycling market | SAM $45M — designers specializing in garment reuse | SOM $3M — NFT adoption for upcycled product verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Upcycle Proof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs for upcycled garment transformations as proof of sustainable creative ownership. Discipline: Fashion & Textile Design (sustainable design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokens give verifiable provenance for altered or recycled fashion pieces. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Upcycle Proof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-accessory-archives-12 Title: Accessory Archives Theme: Fashion & Textile Design (fashion) · jewelry design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Digitally mint exclusive NFT records of original jewelry designs with provenance. Why Hedera: HTS NFT on Hedera testnet links unique design CIDs to verified creators immutably. Market: TAM $400M — jewelry design software market | SAM $110M — independent jewelry designers | SOM $7M — NFT use for jewelry provenance tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Accessory Archives" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitally mint exclusive NFT records of original jewelry designs with provenance. Discipline: Fashion & Textile Design (jewelry design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT on Hedera testnet links unique design CIDs to verified creators immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Accessory Archives" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-moodboard-mint-13 Title: Moodboard Mint Theme: Fashion & Textile Design (fashion) · concept curation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint fashion moodboards as NFTs to claim original inspiration and concepts. Why Hedera: NFT provenance mints timestamp and verify artwork or collage originality. Market: TAM $300M — creative concept and moodboard tools | SAM $80M — fashion creative directors and stylists | SOM $5M — NFT adoption in creative asset verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Moodboard Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint fashion moodboards as NFTs to claim original inspiration and concepts. Discipline: Fashion & Textile Design (concept curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mints timestamp and verify artwork or collage originality. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Moodboard Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fit-nft-lab-14 Title: Fit NFT Lab Theme: Fashion & Textile Design (fashion) · virtual fitting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint verified NFTs representing customizable fit parameters for digital wardrobe pieces. Why Hedera: Immutable tokens anchor each unique fit profile to user and designer provenance. Market: TAM $350M — digital fitting and wardrobe software market | SAM $100M — virtual fashion designers and stylists | SOM $6M — NFT-driven virtual fit solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fit NFT Lab" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint verified NFTs representing customizable fit parameters for digital wardrobe pieces. Discipline: Fashion & Textile Design (virtual fitting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable tokens anchor each unique fit profile to user and designer provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fit NFT Lab" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-runway-record-15 Title: Runway Record Theme: Fashion & Textile Design (fashion) · showcase curation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs documenting runway shows’ looks for authentic digital archiving and resale. Why Hedera: HTS NFT tokens provide unalterable proof for each unique runway outfit presentation. Market: TAM $450M — fashion event and showcase software | SAM $130M — show producers and designers | SOM $8M — NFT provenance for runway digital archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Runway Record" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs documenting runway shows’ looks for authentic digital archiving and resale. Discipline: Fashion & Textile Design (showcase curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide unalterable proof for each unique runway outfit presentation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Runway Record" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-fabric-remix-16 Title: Fabric Remix Theme: Fashion & Textile Design (fashion) · textile remixing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs for remixed fabric patterns proving originality and creative lineage. Why Hedera: Onchain provenance confirms derivative works anchored to original textile creators. Market: TAM $270M — textile remix and mashup software | SAM $75M — textile artists experimenting with pattern fusion | SOM $5M — NFT-based remix pattern authentication ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fabric Remix" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs for remixed fabric patterns proving originality and creative lineage. Discipline: Fashion & Textile Design (textile remixing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain provenance confirms derivative works anchored to original textile creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fabric Remix" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-sustainable-stitch-17 Title: Sustainable Stitch Theme: Fashion & Textile Design (fashion) · zero-waste design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs proving zero-waste garment designs for fashion sustainability verification. Why Hedera: Immutable provenance mints validate zero-waste design authenticity and ownership. Market: TAM $200M — zero-waste apparel design market | SAM $60M — eco-conscious fashion designers | SOM $4M — NFT use in zero-waste design attribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sustainable Stitch" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs proving zero-waste garment designs for fashion sustainability verification. Discipline: Fashion & Textile Design (zero-waste design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable provenance mints validate zero-waste design authenticity and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sustainable Stitch" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-digital-drapes-18 Title: Digital Drapes Theme: Fashion & Textile Design (fashion) · 3D garment design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFTs certifying original digital drape and garment simulations for designers. Why Hedera: HTS NFT tokens link 3D simulation files to verified creators on IPFS. Market: TAM $500M — 3D fashion design software market | SAM $140M — virtual garment innovators and stylists | SOM $9M — NFT adoption for 3D design provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Digital Drapes" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs certifying original digital drape and garment simulations for designers. Discipline: Fashion & Textile Design (3D garment design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link 3D simulation files to verified creators on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Digital Drapes" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-ethnic-essence-19 Title: Ethnic Essence Theme: Fashion & Textile Design (fashion) · cultural textiles Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs to protect and showcase indigenous and ethnic textile designs. Why Hedera: NFT provenance mints preserve cultural IP and provenance securely and transparently. Market: TAM $350M — ethnic textile and craft markets | SAM $90M — designers specializing in cultural textiles | SOM $6M — NFT use for cultural heritage textile protection ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Ethnic Essence" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs to protect and showcase indigenous and ethnic textile designs. Discipline: Fashion & Textile Design (cultural textiles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mints preserve cultural IP and provenance securely and transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Ethnic Essence" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-accessory-authenticator-20 Title: Accessory Authenticator Theme: Fashion & Textile Design (fashion) · limited-edition accessories Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique NFTs proving authenticity and limited runs of accessory designs. Why Hedera: HTS NFT tokens guarantee verifiable scarcity and provenance onchain. Market: TAM $320M — accessory design and retail software | SAM $95M — fashion brands producing exclusive accessories | SOM $7M — NFT-driven authentication in accessories ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Accessory Authenticator" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique NFTs proving authenticity and limited runs of accessory designs. Discipline: Fashion & Textile Design (limited-edition accessories). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens guarantee verifiable scarcity and provenance onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Accessory Authenticator" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-craft-chain-connect-21 Title: Craft Chain Connect Theme: Fashion & Textile Design (fashion) · artisan collaboration Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint collaborative NFTs documenting co-creation between designers and artisans. Why Hedera: NFTs record joint provenance and IP ownership on immutable blockchain records. Market: TAM $280M — artisan-designer collaboration platforms | SAM $75M — fashion designers working with craftspeople | SOM $5M — NFT adoption in co-created fashion products ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Craft Chain Connect" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint collaborative NFTs documenting co-creation between designers and artisans. Discipline: Fashion & Textile Design (artisan collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs record joint provenance and IP ownership on immutable blockchain records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Craft Chain Connect" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-virtual-vogue-vault-22 Title: Virtual Vogue Vault Theme: Fashion & Textile Design (fashion) · digital fashion archives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFT archives of vintage and digital fashion pieces for authenticated preservation. Why Hedera: HTS NFT tokens link archival IPFS content immutably to creators and curators. Market: TAM $400M — digital fashion archive services | SAM $110M — fashion museums and digital curators | SOM $7M — NFT use in fashion archival digitization ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Vogue Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFT archives of vintage and digital fashion pieces for authenticated preservation. Discipline: Fashion & Textile Design (digital fashion archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link archival IPFS content immutably to creators and curators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Vogue Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-print-proofs-23 Title: Print Proofs Theme: Fashion & Textile Design (fashion) · fabric print designs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs to prove original fabric print designs’ authorship and exclusivity. Why Hedera: NFT minting anchors each print design with verifiable creator metadata. Market: TAM $260M — fabric print design market | SAM $70M — freelance textile print designers | SOM $4M — NFT-based print design exclusivity verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Print Proofs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs to prove original fabric print designs’ authorship and exclusivity. Discipline: Fashion & Textile Design (fabric print designs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting anchors each print design with verifiable creator metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Print Proofs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA fashion-style-storyline-24 Title: Style Storyline Theme: Fashion & Textile Design (fashion) · fashion storytelling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs encoding fashion collection narratives as immutable digital assets. Why Hedera: HTS NFT provenance mints protect unique brand stories linked to collections. Market: TAM $350M — fashion brand storytelling tools | SAM $90M — fashion marketers and brand storytellers | SOM $6M — NFT adoption in narrative ownership for fashion ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Storyline" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs encoding fashion collection narratives as immutable digital assets. Discipline: Fashion & Textile Design (fashion storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT provenance mints protect unique brand stories linked to collections. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Storyline" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Filmmaking & Animation filmmakers, animators, motion designers, storyboard artists ============================================================================== ------------------------------------------------------------------------------ IDEA film-animation-framechain-ledger-0 Title: FrameChain Ledger Theme: Filmmaking & Animation (film-animation) · frame provenance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Verify animation frames' originality and authorship on an immutable blockchain ledger. Why Hedera: Hedera testnet smart contracts provide transparent, tamper-proof recording of frame ownership and modifications. Market: TAM $400B — global animation and filmmaking industry | SAM $3B — digital asset provenance solutions for animation | SOM $50M — blockchain-based IP verification tools for animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameChain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify animation frames' originality and authorship on an immutable blockchain ledger. Discipline: Filmmaking & Animation (frame provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide transparent, tamper-proof recording of frame ownership and modifications. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameChain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-scriptmint-1 Title: ScriptMint Theme: Filmmaking & Animation (film-animation) · script rights tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and transfer screenplay rights securely through blockchain for transparent ownership history. Why Hedera: Smart contracts automate rights transfer and record it immutably on Hedera testnet. Market: TAM $400B — global filmmaking and script market | SAM $1.2B — digital rights management software | SOM $20M — blockchain DRM for filmmakers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptMint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and transfer screenplay rights securely through blockchain for transparent ownership history. Discipline: Filmmaking & Animation (script rights tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate rights transfer and record it immutably on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptMint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animstake-voting-2 Title: AnimStake Voting Theme: Filmmaking & Animation (film-animation) · crowd animation funding Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable decentralized community voting to fund independent animations using secure onchain ballots. Why Hedera: Hedera testnet contracts reliably manage voting and fund escrow without trusted intermediaries. Market: TAM $400B — global animation funding market | SAM $500M — crowdfunding platforms for creative projects | SOM $10M — onchain voting tools for animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimStake Voting" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable decentralized community voting to fund independent animations using secure onchain ballots. Discipline: Filmmaking & Animation (crowd animation funding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts reliably manage voting and fund escrow without trusted intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimStake Voting" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-motionnft-vault-3 Title: MotionNFT Vault Theme: Filmmaking & Animation (film-animation) · motion asset NFTs Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint and trade unique motion design assets as NFTs with verifiable ownership and licensing. Why Hedera: Hedera testnet contracts enable minting and tracking of unique motion NFTs on a secure blockchain. Market: TAM $400B — global animation assets market | SAM $2B — NFT marketplace for digital creatives | SOM $30M — NFT licensing for motion designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionNFT Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade unique motion design assets as NFTs with verifiable ownership and licensing. Discipline: Filmmaking & Animation (motion asset NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable minting and tracking of unique motion NFTs on a secure blockchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionNFT Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboardchain-4 Title: StoryboardChain Theme: Filmmaking & Animation (film-animation) · storyboard authenticity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record storyboard versions onchain to prove originality and evolution of narrative visuals. Why Hedera: Hedera testnet smart contracts immutably track and timestamp artwork revisions securely. Market: TAM $400B — global pre-production market | SAM $750M — digital storyboard software | SOM $15M — blockchain-based storyboard verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryboardChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record storyboard versions onchain to prove originality and evolution of narrative visuals. Discipline: Filmmaking & Animation (storyboard authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts immutably track and timestamp artwork revisions securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryboardChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-licenselock-5 Title: LicenseLock Theme: Filmmaking & Animation (film-animation) · license management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage animation asset licenses securely and transparently on a blockchain smart contract. Why Hedera: Hedera testnet contracts automate license issuance and expiry with tamper-proof records. Market: TAM $400B — global animation licensing industry | SAM $1.5B — digital rights and licenses software | SOM $25M — onchain license management for creatives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LicenseLock" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage animation asset licenses securely and transparently on a blockchain smart contract. Discipline: Filmmaking & Animation (license management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automate license issuance and expiry with tamper-proof records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LicenseLock" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animcred-score-6 Title: AnimCred Score Theme: Filmmaking & Animation (film-animation) · creator reputation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Build transparent, verifiable reputation scores for filmmakers and animators using onchain activity. Why Hedera: Hedera testnet smart contracts securely aggregate and verify user contributions and feedback. Market: TAM $400B — global creative labor market | SAM $600M — reputation and credential platforms | SOM $12M — blockchain reputation systems for animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimCred Score" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Build transparent, verifiable reputation scores for filmmakers and animators using onchain activity. Discipline: Filmmaking & Animation (creator reputation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts securely aggregate and verify user contributions and feedback. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimCred Score" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-sceneswap-dex-7 Title: SceneSwap DEX Theme: Filmmaking & Animation (film-animation) · asset exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Trade and swap 3D scenes and animation assets peer-to-peer with secure onchain escrow. Why Hedera: Hedera testnet contracts provide trustless atomic swaps and automated payments for digital assets. Market: TAM $400B — global animation asset market | SAM $1.8B — digital asset marketplaces | SOM $28M — onchain asset trading platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneSwap DEX" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade and swap 3D scenes and animation assets peer-to-peer with secure onchain escrow. Discipline: Filmmaking & Animation (asset exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide trustless atomic swaps and automated payments for digital assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneSwap DEX" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animguild-dao-8 Title: AnimGuild DAO Theme: Filmmaking & Animation (film-animation) · community governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Empower animation communities with decentralized autonomous organizations for project decisions. Why Hedera: Hedera testnet smart contracts enable transparent, rule-based voting and fund allocation. Market: TAM $400B — global animation industry | SAM $300M — creative DAOs and communities | SOM $7M — blockchain governance tools for animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimGuild DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Empower animation communities with decentralized autonomous organizations for project decisions. Discipline: Filmmaking & Animation (community governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable transparent, rule-based voting and fund allocation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimGuild DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-renderstake-9 Title: RenderStake Theme: Filmmaking & Animation (film-animation) · distributed rendering Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralize animation rendering jobs and pay nodes through secure smart contract escrow. Why Hedera: Hedera testnet contracts automate fair task distribution and payment release upon verification. Market: TAM $400B — global animation rendering market | SAM $900M — cloud rendering services | SOM $18M — blockchain-based rendering networks ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RenderStake" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize animation rendering jobs and pay nodes through secure smart contract escrow. Discipline: Filmmaking & Animation (distributed rendering). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automate fair task distribution and payment release upon verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RenderStake" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animproof-timestamp-10 Title: AnimProof Timestamp Theme: Filmmaking & Animation (film-animation) · work timestamping Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Timestamp animation works to prove creation dates and prevent IP disputes with onchain records. Why Hedera: Hedera testnet smart contracts create immutable, verifiable proof of creation time. Market: TAM $400B — global animation IP market | SAM $2B — IP protection services | SOM $35M — blockchain timestamping for creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimProof Timestamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Timestamp animation works to prove creation dates and prevent IP disputes with onchain records. Discipline: Filmmaking & Animation (work timestamping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts create immutable, verifiable proof of creation time. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimProof Timestamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-voicechain-sync-11 Title: VoiceChain Sync Theme: Filmmaking & Animation (film-animation) · voice sync verification Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely synchronize and timestamp voice acting tracks with animation frames on blockchain. Why Hedera: Hedera testnet contracts ensure immutable matching records of audio and frame data. Market: TAM $400B — animation and dubbing industry | SAM $400M — audio-video synchronization tools | SOM $8M — blockchain verification for voice sync ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VoiceChain Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely synchronize and timestamp voice acting tracks with animation frames on blockchain. Discipline: Filmmaking & Animation (voice sync verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts ensure immutable matching records of audio and frame data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VoiceChain Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animtip-jar-12 Title: AnimTip Jar Theme: Filmmaking & Animation (film-animation) · micro-donations Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Let fans tip animators instantly and transparently via onchain micro-payment smart contracts. Why Hedera: Hedera testnet enables low-cost, immutable transactions directly supporting creators. Market: TAM $400B — global animation audience economy | SAM $700M — digital creator monetization | SOM $9M — onchain tipping apps for filmmakers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimTip Jar" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Let fans tip animators instantly and transparently via onchain micro-payment smart contracts. Discipline: Filmmaking & Animation (micro-donations). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet enables low-cost, immutable transactions directly supporting creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimTip Jar" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-charanim-nft-13 Title: CharAnim NFT Theme: Filmmaking & Animation (film-animation) · character IP NFT Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint trademarked animation characters as NFTs to secure and trade intellectual property rights. Why Hedera: Hedera testnet smart contracts enforce provenance and exclusive ownership of characters. Market: TAM $400B — animation character licensing market | SAM $1B — IP-based NFT collectibles | SOM $22M — character NFT licensing platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CharAnim NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint trademarked animation characters as NFTs to secure and trade intellectual property rights. Discipline: Filmmaking & Animation (character IP NFT). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enforce provenance and exclusive ownership of characters. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CharAnim NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animlesson-chain-14 Title: AnimLesson Chain Theme: Filmmaking & Animation (film-animation) · educational credentialing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Certify animation course completions and skills with blockchain-verified digital diplomas. Why Hedera: Hedera testnet contracts provide tamper-proof certificates accessible globally and instantly. Market: TAM $400B — global animation education market | SAM $300M — e-learning credential platforms | SOM $6M — onchain certification for animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimLesson Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Certify animation course completions and skills with blockchain-verified digital diplomas. Discipline: Filmmaking & Animation (educational credentialing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide tamper-proof certificates accessible globally and instantly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimLesson Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animcontest-dao-15 Title: AnimContest DAO Theme: Filmmaking & Animation (film-animation) · competition governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Run transparent animation contests with onchain submission tracking and automated prize distribution. Why Hedera: Hedera testnet smart contracts guarantee fair, verifiable competition results and payouts. Market: TAM $400B — global animation contest market | SAM $200M — digital creative contests | SOM $5M — blockchain contest platforms for filmmakers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimContest DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Run transparent animation contests with onchain submission tracking and automated prize distribution. Discipline: Filmmaking & Animation (competition governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts guarantee fair, verifiable competition results and payouts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimContest DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animscript-oracles-16 Title: AnimScript Oracles Theme: Filmmaking & Animation (film-animation) · script verification Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Use blockchain oracles to verify external animation script data integrity and authenticity onchain. Why Hedera: Hedera testnet contracts integrate external data securely with verifiable authenticity via oracles. Market: TAM $400B — scriptwriting and animation industry | SAM $500M — blockchain oracle services | SOM $8M — script integrity verification tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimScript Oracles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use blockchain oracles to verify external animation script data integrity and authenticity onchain. Discipline: Filmmaking & Animation (script verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts integrate external data securely with verifiable authenticity via oracles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimScript Oracles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animroyalties-17 Title: AnimRoyalties Theme: Filmmaking & Animation (film-animation) · royalty distribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate royalty payments for animation collaborators with transparent, onchain smart contracts. Why Hedera: Hedera testnet contracts enforce preset royalty splits and instant payouts securely. Market: TAM $400B — global animation revenue stream | SAM $1B — royalty management software | SOM $20M — blockchain royalty automation for creatives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimRoyalties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate royalty payments for animation collaborators with transparent, onchain smart contracts. Discipline: Filmmaking & Animation (royalty distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enforce preset royalty splits and instant payouts securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimRoyalties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-scenemood-chain-18 Title: SceneMood Chain Theme: Filmmaking & Animation (film-animation) · color grading proof Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record color grading versions on blockchain to secure post-production creative decisions. Why Hedera: Hedera testnet contracts timestamp and preserve immutable color study records. Market: TAM $400B — post-production market | SAM $600M — color grading software | SOM $12M — blockchain-based creative proofing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneMood Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record color grading versions on blockchain to secure post-production creative decisions. Discipline: Filmmaking & Animation (color grading proof). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts timestamp and preserve immutable color study records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneMood Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animbadge-awards-19 Title: AnimBadge Awards Theme: Filmmaking & Animation (film-animation) · industry recognition Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue blockchain-verified badges and awards recognizing animator skills and achievements. Why Hedera: Hedera testnet smart contracts create immutable, forgery-proof industry credentials. Market: TAM $400B — animation professional development | SAM $250M — credentialing and awards market | SOM $4M — blockchain awards platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimBadge Awards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue blockchain-verified badges and awards recognizing animator skills and achievements. Discipline: Filmmaking & Animation (industry recognition). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts create immutable, forgery-proof industry credentials. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimBadge Awards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animframe-trade-20 Title: AnimFrame Trade Theme: Filmmaking & Animation (film-animation) · frame licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: License individual animation frames securely and transparently using blockchain smart contracts. Why Hedera: Hedera testnet contracts automate license terms and prove usage rights immutably. Market: TAM $400B — animation licensing market | SAM $800M — digital asset licensing | SOM $15M — blockchain frame licensing tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimFrame Trade" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License individual animation frames securely and transparently using blockchain smart contracts. Discipline: Filmmaking & Animation (frame licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automate license terms and prove usage rights immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimFrame Trade" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animpitch-fund-21 Title: AnimPitch Fund Theme: Filmmaking & Animation (film-animation) · project pitching Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Pitch animation projects to decentralized investors with onchain proposals and funding escrow. Why Hedera: Hedera testnet smart contracts ensure transparent funding and project milestone tracking. Market: TAM $400B — animation financing market | SAM $400M — crowdfunding for films and animation | SOM $7M — blockchain pitching platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimPitch Fund" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pitch animation projects to decentralized investors with onchain proposals and funding escrow. Discipline: Filmmaking & Animation (project pitching). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure transparent funding and project milestone tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimPitch Fund" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-licenchain-tracker-22 Title: LicenChain Tracker Theme: Filmmaking & Animation (film-animation) · license audit trail Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Maintain an immutable audit trail of animation licenses and transfers using smart contracts. Why Hedera: Hedera testnet contracts provide transparent, verifiable histories of license transactions. Market: TAM $400B — global animation licensing market | SAM $1.3B — license tracking solutions | SOM $18M — blockchain license audit software ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LicenChain Tracker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Maintain an immutable audit trail of animation licenses and transfers using smart contracts. Discipline: Filmmaking & Animation (license audit trail). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide transparent, verifiable histories of license transactions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LicenChain Tracker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animmetamanager-23 Title: AnimMetaManager Theme: Filmmaking & Animation (film-animation) · metadata control Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely store and update animation metadata onchain to maintain provenance and rights data. Why Hedera: Hedera testnet smart contracts ensure metadata integrity and public verification. Market: TAM $400B — animation asset management | SAM $700M — metadata platforms for creatives | SOM $10M — onchain metadata tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimMetaManager" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and update animation metadata onchain to maintain provenance and rights data. Discipline: Filmmaking & Animation (metadata control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure metadata integrity and public verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimMetaManager" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animcollab-chain-24 Title: AnimCollab Chain Theme: Filmmaking & Animation (film-animation) · collaborative workflows Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Coordinate multi-artist animation projects with onchain task assignments and progress tracking. Why Hedera: Hedera testnet contracts enable secure, transparent collaboration records and payments. Market: TAM $400B — animation production market | SAM $900M — project management software for creatives | SOM $14M — blockchain collaboration tools for animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimCollab Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Coordinate multi-artist animation projects with onchain task assignments and progress tracking. Discipline: Filmmaking & Animation (collaborative workflows). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable secure, transparent collaboration records and payments. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimCollab Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-frameforge-archive-0 Title: FrameForge Archive Theme: Filmmaking & Animation (film-animation) · storyboard management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely store and share storyboards as immutable IPFS manifests to simplify team collaboration. Why Hedera: Pinata JWT ensures permanent, decentralized storage of visual storyboards and metadata. Market: TAM $1B — global digital storyboard software market | SAM $300M — cloud-based collaborative previsualization tools | SOM $50M — indie and freelance filmmaker storyboard users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameForge Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and share storyboards as immutable IPFS manifests to simplify team collaboration. Discipline: Filmmaking & Animation (storyboard management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures permanent, decentralized storage of visual storyboards and metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameForge Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-texturevault-1 Title: TextureVault Theme: Filmmaking & Animation (film-animation) · material asset library Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and catalog textures on IPFS for reuse and verified provenance in animation projects. Why Hedera: Pinata JWT gives permanent decentralized image hosting, enabling trustworthy asset reuse. Market: TAM $800M — global 3D asset marketplaces | SAM $200M — texture libraries for animation studios | SOM $40M — freelance artist asset sharing platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TextureVault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and catalog textures on IPFS for reuse and verified provenance in animation projects. Discipline: Filmmaking & Animation (material asset library). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT gives permanent decentralized image hosting, enabling trustworthy asset reuse. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TextureVault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animscene-sync-2 Title: AnimScene Sync Theme: Filmmaking & Animation (film-animation) · scene version control Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Automatically pin scene JSON manifests to IPFS to track animation iterations and changes. Why Hedera: Pinata JWT offers secure, immutable scene versioning independent of centralized servers. Market: TAM $600M — animation production management tools | SAM $150M — scene version tracking software | SOM $25M — small studio adoption of cloud scene versioning ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimScene Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically pin scene JSON manifests to IPFS to track animation iterations and changes. Discipline: Filmmaking & Animation (scene version control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT offers secure, immutable scene versioning independent of centralized servers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimScene Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-moodboardchain-3 Title: MoodboardChain Theme: Filmmaking & Animation (film-animation) · color study curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create decentralized moodboards pinned to IPFS for collaborative color grading projects. Why Hedera: Pinata JWT ensures moodboards remain accessible and unaltered during creative processes. Market: TAM $400M — digital moodboard platforms | SAM $100M — collaborative color grading tools | SOM $15M — freelance colorists and animators using moodboards ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoodboardChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create decentralized moodboards pinned to IPFS for collaborative color grading projects. Discipline: Filmmaking & Animation (color study curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures moodboards remain accessible and unaltered during creative processes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoodboardChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-rigpin-sync-4 Title: RigPin Sync Theme: Filmmaking & Animation (film-animation) · character rig sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Share and pin character rig files and metadata on IPFS for verified reuse across teams. Why Hedera: Pinata JWT guarantees persistent hosting of large rig files and version history. Market: TAM $350M — rigging software market | SAM $90M — rig sharing platforms | SOM $10M — mid-sized animation studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RigPin Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share and pin character rig files and metadata on IPFS for verified reuse across teams. Discipline: Filmmaking & Animation (character rig sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT guarantees persistent hosting of large rig files and version history. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RigPin Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-loop-provenance-5 Title: Loop Provenance Theme: Filmmaking & Animation (film-animation) · animation loop libraries Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin looped animations on IPFS to provide artists with immutable, reusable animation cycles. Why Hedera: Pinata JWT enables permanent hosting and easy retrieval of loop animation assets. Market: TAM $250M — animation cycle marketplaces | SAM $70M — loop library subscriptions | SOM $8M — indie animator userbase ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Loop Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin looped animations on IPFS to provide artists with immutable, reusable animation cycles. Discipline: Filmmaking & Animation (animation loop libraries). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT enables permanent hosting and easy retrieval of loop animation assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Loop Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-scenemetastore-6 Title: SceneMetaStore Theme: Filmmaking & Animation (film-animation) · metadata tagging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Attach and pin rich JSON metadata to animation scenes, ensuring permanent scene context storage. Why Hedera: Pinata JWT's JSON pinning secures metadata's immutability and accessibility forever. Market: TAM $500M — animation project management | SAM $130M — metadata enrichment tools | SOM $20M — small studio metadata adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneMetaStore" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach and pin rich JSON metadata to animation scenes, ensuring permanent scene context storage. Discipline: Filmmaking & Animation (metadata tagging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT's JSON pinning secures metadata's immutability and accessibility forever. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneMetaStore" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboardchain-7 Title: StoryboardChain Theme: Filmmaking & Animation (film-animation) · distributed storyboarding Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin storyboards and scripts to IPFS for tamper-proof, collaborative episodic planning. Why Hedera: Pinata JWT secures permanent, decentralized storage of evolving creative documents. Market: TAM $1B — collaborative storytelling tools | SAM $250M — episodic production platforms | SOM $35M — indie episodic creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryboardChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin storyboards and scripts to IPFS for tamper-proof, collaborative episodic planning. Discipline: Filmmaking & Animation (distributed storyboarding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT secures permanent, decentralized storage of evolving creative documents. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryboardChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animprops-ledger-8 Title: AnimProps Ledger Theme: Filmmaking & Animation (film-animation) · prop asset management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Immutable pinning of prop designs on IPFS enables verified ownership and reuse across projects. Why Hedera: Pinata JWT allows permanent pinning of image and JSON manifests representing props. Market: TAM $300M — 3D prop asset market | SAM $75M — prop sharing platforms | SOM $12M — niche animation studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimProps Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Immutable pinning of prop designs on IPFS enables verified ownership and reuse across projects. Discipline: Filmmaking & Animation (prop asset management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT allows permanent pinning of image and JSON manifests representing props. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimProps Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-palettechain-9 Title: PaletteChain Theme: Filmmaking & Animation (film-animation) · color palette sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin color palettes on IPFS for permanent, shareable references in collaborative projects. Why Hedera: Pinata JWT's image and JSON pinning enables trustable, decentralized palette hosting. Market: TAM $200M — digital color tools | SAM $50M — shared palette libraries | SOM $7M — motion designers and colorists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PaletteChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin color palettes on IPFS for permanent, shareable references in collaborative projects. Discipline: Filmmaking & Animation (color palette sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT's image and JSON pinning enables trustable, decentralized palette hosting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PaletteChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animdata-store-10 Title: AnimData Store Theme: Filmmaking & Animation (film-animation) · animation data archival Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin keyframe data and animation parameters to IPFS for permanent archival and collaboration. Why Hedera: Pinata JWT secures permanent JSON pinning of crucial animation datasets. Market: TAM $700M — animation production software | SAM $180M — data archiving solutions | SOM $30M — studio archival users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimData Store" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin keyframe data and animation parameters to IPFS for permanent archival and collaboration. Discipline: Filmmaking & Animation (animation data archival). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT secures permanent JSON pinning of crucial animation datasets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimData Store" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-motionmap-vault-11 Title: MotionMap Vault Theme: Filmmaking & Animation (film-animation) · motion capture storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and pin motion capture data on IPFS ensuring permanent access and tamper-proof records. Why Hedera: Pinata JWT handles large JSON/binary data ensuring decentralized motion data permanence. Market: TAM $1.2B — motion capture market | SAM $400M — cloud mocap storage services | SOM $60M — indie studios and artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionMap Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and pin motion capture data on IPFS ensuring permanent access and tamper-proof records. Discipline: Filmmaking & Animation (motion capture storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT handles large JSON/binary data ensuring decentralized motion data permanence. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionMap Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animvoice-ledger-12 Title: AnimVoice Ledger Theme: Filmmaking & Animation (film-animation) · voice asset management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin voiceover takes and metadata to IPFS to maintain immutable, verifiable audio assets. Why Hedera: Pinata JWT supports large media pinning and JSON metadata for permanent voice asset storage. Market: TAM $900M — audio asset management | SAM $250M — voiceover libraries | SOM $35M — freelance voice artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimVoice Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin voiceover takes and metadata to IPFS to maintain immutable, verifiable audio assets. Discipline: Filmmaking & Animation (voice asset management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT supports large media pinning and JSON metadata for permanent voice asset storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimVoice Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-vfxchain-sync-13 Title: VFXChain Sync Theme: Filmmaking & Animation (film-animation) · effects asset curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin VFX layers and settings on IPFS for permanent, sharable effects asset libraries. Why Hedera: Pinata JWT allows immutable storage of complex JSON manifests representing effects layers. Market: TAM $1B — VFX production tools | SAM $300M — VFX asset marketplaces | SOM $45M — mid-size studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VFXChain Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin VFX layers and settings on IPFS for permanent, sharable effects asset libraries. Discipline: Filmmaking & Animation (effects asset curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT allows immutable storage of complex JSON manifests representing effects layers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VFXChain Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animscript-pin-14 Title: AnimScript Pin Theme: Filmmaking & Animation (film-animation) · script integration Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin scripts and screenplays as JSON manifests on IPFS for verifiable, collaborative storytelling. Why Hedera: Pinata JWT secures permanent, decentralized text and JSON hosting for script files. Market: TAM $800M — scriptwriting software | SAM $200M — collaborative script platforms | SOM $30M — indie creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimScript Pin" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin scripts and screenplays as JSON manifests on IPFS for verifiable, collaborative storytelling. Discipline: Filmmaking & Animation (script integration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT secures permanent, decentralized text and JSON hosting for script files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimScript Pin" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animcollab-hub-15 Title: AnimCollab Hub Theme: Filmmaking & Animation (film-animation) · team project syncing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Synchronize animation team files by pinning project manifests on IPFS for secure multiuser updates. Why Hedera: Pinata JWT provides immutable, shared project state storage decentralized over IPFS. Market: TAM $1.5B — collaborative animation tools | SAM $400M — project management software | SOM $50M — small to mid-size studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimCollab Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Synchronize animation team files by pinning project manifests on IPFS for secure multiuser updates. Discipline: Filmmaking & Animation (team project syncing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT provides immutable, shared project state storage decentralized over IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimCollab Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboard-replay-16 Title: Storyboard Replay Theme: Filmmaking & Animation (film-animation) · animatic playback Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin animatic sequences and frames on IPFS for permanent, shareable playback references. Why Hedera: Pinata JWT enables permanent storage of image sequences and JSON timing manifests. Market: TAM $600M — animatic production tools | SAM $150M — animatic asset libraries | SOM $20M — freelance animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Replay" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin animatic sequences and frames on IPFS for permanent, shareable playback references. Discipline: Filmmaking & Animation (animatic playback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT enables permanent storage of image sequences and JSON timing manifests. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Replay" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-modelpin-archive-17 Title: ModelPin Archive Theme: Filmmaking & Animation (film-animation) · 3D model documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin 3D model metadata and previews on IPFS for permanent documentation and usage logs. Why Hedera: Pinata JWT handles image and JSON pinning ensuring persistent, verifiable model records. Market: TAM $1B — 3D modeling platforms | SAM $300M — model asset documentation | SOM $40M — mid-tier studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ModelPin Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin 3D model metadata and previews on IPFS for permanent documentation and usage logs. Discipline: Filmmaking & Animation (3D model documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT handles image and JSON pinning ensuring persistent, verifiable model records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ModelPin Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animfont-locker-18 Title: AnimFont Locker Theme: Filmmaking & Animation (film-animation) · typography asset vault Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin custom fonts and typographic assets to IPFS for permanent reuse in animated titles. Why Hedera: Pinata JWT ensures immutable hosting of font files and JSON usage data. Market: TAM $400M — font licensing market | SAM $100M — typographic asset portals | SOM $15M — motion designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimFont Locker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin custom fonts and typographic assets to IPFS for permanent reuse in animated titles. Discipline: Filmmaking & Animation (typography asset vault). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures immutable hosting of font files and JSON usage data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimFont Locker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-lipsync-ledger-19 Title: LipSync Ledger Theme: Filmmaking & Animation (film-animation) · lipsync data storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin lipsync timing data to IPFS for permanent, sharable, and verifiable sync assets. Why Hedera: Pinata JWT supports JSON pinning for immutable lipsync data storage. Market: TAM $500M — animation lipsync tools | SAM $120M — lipsync data marketplaces | SOM $18M — indie and freelance animators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LipSync Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin lipsync timing data to IPFS for permanent, sharable, and verifiable sync assets. Discipline: Filmmaking & Animation (lipsync data storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT supports JSON pinning for immutable lipsync data storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LipSync Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animshot-library-20 Title: AnimShot Library Theme: Filmmaking & Animation (film-animation) · shot list archival Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin shot lists and planning JSON manifests on IPFS to ensure permanent, tamper-proof records. Why Hedera: Pinata JWT provides secure, decentralized shot metadata storage immutable over time. Market: TAM $700M — shot management software | SAM $200M — production planning tools | SOM $25M — small production houses ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimShot Library" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin shot lists and planning JSON manifests on IPFS to ensure permanent, tamper-proof records. Discipline: Filmmaking & Animation (shot list archival). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT provides secure, decentralized shot metadata storage immutable over time. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimShot Library" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-propchain-share-21 Title: PropChain Share Theme: Filmmaking & Animation (film-animation) · prop design collaboration Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Collaboratively design and pin prop assets on IPFS with permanent version history. Why Hedera: Pinata JWT enables immutable pinning supporting collaborative asset provenance. Market: TAM $350M — 3D prop design software | SAM $90M — prop collaboration platforms | SOM $12M — niche animation teams ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropChain Share" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaboratively design and pin prop assets on IPFS with permanent version history. Discipline: Filmmaking & Animation (prop design collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT enables immutable pinning supporting collaborative asset provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropChain Share" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboard-captioner-22 Title: Storyboard Captioner Theme: Filmmaking & Animation (film-animation) · dialogue annotation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin dialogue captions linked to storyboard frames on IPFS for permanent dialogue-frame sync. Why Hedera: Pinata JWT's JSON pinning offers decentralized, permanent dialogue annotation storage. Market: TAM $450M — subtitling and captioning tools | SAM $110M — script annotation platforms | SOM $14M — indie animation projects ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Captioner" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin dialogue captions linked to storyboard frames on IPFS for permanent dialogue-frame sync. Discipline: Filmmaking & Animation (dialogue annotation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT's JSON pinning offers decentralized, permanent dialogue annotation storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Captioner" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animframe-swap-23 Title: AnimFrame Swap Theme: Filmmaking & Animation (film-animation) · frame trading marketplace Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and trade individual animation frames on IPFS for verified ownership and provenance. Why Hedera: Pinata JWT secures immutable, decentralized frame asset pinning with permanent CIDs. Market: TAM $300M — frame asset marketplaces | SAM $80M — independent frame trading | SOM $10M — freelance animator users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimFrame Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and trade individual animation frames on IPFS for verified ownership and provenance. Discipline: Filmmaking & Animation (frame trading marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT secures immutable, decentralized frame asset pinning with permanent CIDs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimFrame Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboard-sync-0 Title: Storyboard Sync Theme: Filmmaking & Animation (film-animation) · storyboard collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable real-time, gasless syncing and feedback on storyboards among filmmakers and artists. Why Hedera: the embedded wallet’s social wallet plus Hedera's fixed sub-cent fees allows seamless, user-friendly collaborative edits without blockchain friction. Market: TAM $3B — global digital storyboard tools market | SAM $500M — SaaS for collaborative pre-production software | SOM $10M — early adopters in blockchain-enabled creative tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable real-time, gasless syncing and feedback on storyboards among filmmakers and artists. Discipline: Filmmaking & Animation (storyboard collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s social wallet plus Hedera's fixed sub-cent fees allows seamless, user-friendly collaborative edits without blockchain friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animatic-drops-1 Title: Animatic Drops Theme: Filmmaking & Animation (film-animation) · animatic distribution Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute animatics securely with embedded wallets for easy user access and sponsored preview transactions. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees provide frictionless previews and social sharing without gas fees. Market: TAM $2B — animation production content distribution | SAM $400M — animatic and pitch distribution platforms | SOM $8M — blockchain-integrated animation marketing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Animatic Drops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute animatics securely with embedded wallets for easy user access and sponsored preview transactions. Discipline: Filmmaking & Animation (animatic distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees provide frictionless previews and social sharing without gas fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Animatic Drops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-motion-share-2 Title: Motion Share Theme: Filmmaking & Animation (film-animation) · motion design sharing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share and collect feedback on motion graphics with instant, gasless social wallet interactions. Why Hedera: the embedded wallet’s Google sign-in and Hedera's fixed sub-cent fees enable frictionless social engagement for creatives. Market: TAM $1.5B — motion graphics software market | SAM $300M — social collaboration tools for motion designers | SOM $6M — early market for blockchain-enabled feedback ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Motion Share" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share and collect feedback on motion graphics with instant, gasless social wallet interactions. Discipline: Filmmaking & Animation (motion design sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s Google sign-in and Hedera's fixed sub-cent fees enable frictionless social engagement for creatives. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Motion Share" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-frame-rights-3 Title: Frame Rights Theme: Filmmaking & Animation (film-animation) · frame licensing Hedera hook: Magic Link email wallet [wallet UX] Pitch: License individual animation frames securely with embedded wallets to track ownership and transactions gas-free. Why Hedera: Magic Link email sign-ins with Hedera's fixed sub-cent fees facilitate easy, transparent rights transfers without user gas cost. Market: TAM $5B — digital rights management in animation | SAM $1B — licensing platforms for creative assets | SOM $20M — niche blockchain licensing solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Frame Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License individual animation frames securely with embedded wallets to track ownership and transactions gas-free. Discipline: Filmmaking & Animation (frame licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins with Hedera's fixed sub-cent fees facilitate easy, transparent rights transfers without user gas cost. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Frame Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-voice-sync-4 Title: Voice Sync Theme: Filmmaking & Animation (film-animation) · voice-over integration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Sync and verify voice-over sessions onchain with social wallets for smooth, gasless collaboration. Why Hedera: the embedded wallet’s social wallet and Hedera's fixed sub-cent fees streamline voice sync without blockchain UX headaches. Market: TAM $1B — voice-over and dubbing services | SAM $200M — collaboration tools for voice artists | SOM $4M — blockchain for audio collaboration ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Voice Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sync and verify voice-over sessions onchain with social wallets for smooth, gasless collaboration. Discipline: Filmmaking & Animation (voice-over integration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s social wallet and Hedera's fixed sub-cent fees streamline voice sync without blockchain UX headaches. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Voice Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-anim-rights-vault-5 Title: Anim Rights Vault Theme: Filmmaking & Animation (film-animation) · animation IP protection Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely store and prove animation IP ownership and usage rights with the embedded wallet-bootstrapped wallets. Why Hedera: Magic Link email sign-in’s ease and gasless tx ensure artists can protect IP without blockchain friction. Market: TAM $10B — global IP rights management | SAM $2B — animation and media IP tools | SOM $40M — blockchain IP management early adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Anim Rights Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and prove animation IP ownership and usage rights with the embedded wallet-bootstrapped wallets. Discipline: Filmmaking & Animation (animation IP protection). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in’s ease and gasless tx ensure artists can protect IP without blockchain friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Anim Rights Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-render-economy-6 Title: Render Economy Theme: Filmmaking & Animation (film-animation) · distributed rendering Hedera hook: Magic Link email wallet [wallet UX] Pitch: Use gasless social wallets to coordinate and incentivize decentralized animation rendering workers. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees enables seamless payments without user gas barriers. Market: TAM $4B — cloud-based rendering services | SAM $700M — freelance rendering marketplaces | SOM $15M — blockchain-powered payment solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Render Economy" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use gasless social wallets to coordinate and incentivize decentralized animation rendering workers. Discipline: Filmmaking & Animation (distributed rendering). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees enables seamless payments without user gas barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Render Economy" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-loop-provenance-7 Title: Loop Provenance Theme: Filmmaking & Animation (film-animation) · loop animation tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track creation history and ownership of looped animations using gasless onchain wallets. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees removes friction from provenance tracking and transfers. Market: TAM $1B — animation asset provenance market | SAM $200M — digital asset authentication | SOM $5M — blockchain provenance niche ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Loop Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track creation history and ownership of looped animations using gasless onchain wallets. Discipline: Filmmaking & Animation (loop animation tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees removes friction from provenance tracking and transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Loop Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-pitchchain-8 Title: PitchChain Theme: Filmmaking & Animation (film-animation) · film pitch verification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely record and track animation pitch submissions leveraging gasless social wallet authentication. Why Hedera: the embedded wallet’s Google sign-in and Hedera's fixed sub-cent fees ensure easy, verified pitch submission without user gas. Market: TAM $500M — animation pitch and funding platforms | SAM $100M — creative project submission tools | SOM $3M — blockchain pitch verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PitchChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely record and track animation pitch submissions leveraging gasless social wallet authentication. Discipline: Filmmaking & Animation (film pitch verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s Google sign-in and Hedera's fixed sub-cent fees ensure easy, verified pitch submission without user gas. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PitchChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-anim-badge-9 Title: Anim Badge Theme: Filmmaking & Animation (film-animation) · skill certification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Award and verify animation skills and course completions with gasless onchain badges and wallets. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees remove friction from issuing and verifying credentials socially. Market: TAM $2B — online creative education market | SAM $400M — certification platforms for animation | SOM $7M — blockchain credentialing early adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Anim Badge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Award and verify animation skills and course completions with gasless onchain badges and wallets. Discipline: Filmmaking & Animation (skill certification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees remove friction from issuing and verifying credentials socially. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Anim Badge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-framechain-10 Title: FrameChain Theme: Filmmaking & Animation (film-animation) · frame-by-frame tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Record and verify each frame’s creative contribution onchain via seamless social wallet transactions. Why Hedera: the embedded wallet’s embedded wallet plus Hedera's fixed sub-cent fees solves complex contribution tracking gaslessly. Market: TAM $3B — animation production tracking | SAM $600M — collaborative asset management | SOM $12M — blockchain creative tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and verify each frame’s creative contribution onchain via seamless social wallet transactions. Discipline: Filmmaking & Animation (frame-by-frame tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s embedded wallet plus Hedera's fixed sub-cent fees solves complex contribution tracking gaslessly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animateswap-11 Title: AnimateSwap Theme: Filmmaking & Animation (film-animation) · asset trading Hedera hook: Magic Link email wallet [wallet UX] Pitch: Trade animation asset snippets instantly with gasless wallet transactions and social sharing built-in. Why Hedera: Magic Link email sign-ins with Hedera's fixed sub-cent fees allow users to swap assets without gas payment barriers. Market: TAM $6B — digital asset marketplaces | SAM $1.2B — animation asset trading platforms | SOM $25M — blockchain asset exchange early market ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimateSwap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade animation asset snippets instantly with gasless wallet transactions and social sharing built-in. Discipline: Filmmaking & Animation (asset trading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins with Hedera's fixed sub-cent fees allow users to swap assets without gas payment barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimateSwap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-loopsync-12 Title: LoopSync Theme: Filmmaking & Animation (film-animation) · looped animation synchronization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable collaborative editing and playback syncing of loops via gasless wallets and onchain state management. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable seamless multi-user control with zero gas friction. Market: TAM $1.2B — loop animation market globally | SAM $250M — collaborative animation tools | SOM $5M — blockchain-enabled collaboration ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LoopSync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable collaborative editing and playback syncing of loops via gasless wallets and onchain state management. Discipline: Filmmaking & Animation (looped animation synchronization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable seamless multi-user control with zero gas friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LoopSync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-designtoken-flow-13 Title: DesignToken Flow Theme: Filmmaking & Animation (film-animation) · tokenized asset flow Hedera hook: Magic Link email wallet [wallet UX] Pitch: Tokenize animation assets and track their usage flow onchain with gasless user wallet transactions. Why Hedera: the embedded wallet’s social wallet plus Hedera's fixed sub-cent fees makes token flow traceable without user crypto knowledge or fees. Market: TAM $7B — tokenized digital media market | SAM $1.5B — animation asset tokenization services | SOM $30M — blockchain token flow startups ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DesignToken Flow" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize animation assets and track their usage flow onchain with gasless user wallet transactions. Discipline: Filmmaking & Animation (tokenized asset flow). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s social wallet plus Hedera's fixed sub-cent fees makes token flow traceable without user crypto knowledge or fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DesignToken Flow" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animcrew-chain-14 Title: AnimCrew Chain Theme: Filmmaking & Animation (film-animation) · team collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage and reward animation production teams with gasless social wallet-based task and payout tracking. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables smooth team coordination without gas costs. Market: TAM $4B — animation studio management tools | SAM $800M — team collaboration SaaS | SOM $18M — blockchain payout solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimCrew Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and reward animation production teams with gasless social wallet-based task and payout tracking. Discipline: Filmmaking & Animation (team collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables smooth team coordination without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimCrew Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-styleswap-15 Title: StyleSwap Theme: Filmmaking & Animation (film-animation) · animation style exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Exchange and remix animation styles with gasless wallet onboarding and instant transaction sponsorship. Why Hedera: the embedded wallet’s embedded wallet ensures frictionless user experience with zero gas during style swaps. Market: TAM $1B — animation style marketplaces | SAM $200M — digital creative asset exchange | SOM $4M — blockchain style sharing early market ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StyleSwap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Exchange and remix animation styles with gasless wallet onboarding and instant transaction sponsorship. Discipline: Filmmaking & Animation (animation style exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s embedded wallet ensures frictionless user experience with zero gas during style swaps. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StyleSwap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-voicechain-cast-16 Title: VoiceChain Cast Theme: Filmmaking & Animation (film-animation) · cast rights tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track voice actor rights and usage in animation projects via gasless social wallet authentication. Why Hedera: the embedded wallet’s social wallet plus Hedera's fixed sub-cent fees delivers seamless rights management without blockchain complexity. Market: TAM $1.5B — voice actor management | SAM $350M — rights tracking for creatives | SOM $7M — blockchain voice rights platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VoiceChain Cast" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track voice actor rights and usage in animation projects via gasless social wallet authentication. Discipline: Filmmaking & Animation (cast rights tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s social wallet plus Hedera's fixed sub-cent fees delivers seamless rights management without blockchain complexity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VoiceChain Cast" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animchain-feedback-17 Title: AnimChain Feedback Theme: Filmmaking & Animation (film-animation) · creative feedback loops Hedera hook: Magic Link email wallet [wallet UX] Pitch: Capture and timestamp animator feedback on projects securely and gaslessly with the embedded wallet-based wallets. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees simplify onchain feedback without user gas awareness. Market: TAM $800M — animation review & feedback tools | SAM $150M — collaborative creative feedback | SOM $3M — blockchain feedback innovators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimChain Feedback" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Capture and timestamp animator feedback on projects securely and gaslessly with the embedded wallet-based wallets. Discipline: Filmmaking & Animation (creative feedback loops). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees simplify onchain feedback without user gas awareness. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimChain Feedback" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-metascene-ledger-18 Title: MetaScene Ledger Theme: Filmmaking & Animation (film-animation) · scene metadata tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track all metadata changes in animation scenes onchain with gasless wallet updates for full provenance. Why Hedera: the embedded wallet’s social wallet and Hedera's fixed sub-cent fees remove gas barriers to continuous onchain metadata logging. Market: TAM $2B — animation production metadata | SAM $400M — digital asset metadata services | SOM $9M — blockchain metadata tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MetaScene Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track all metadata changes in animation scenes onchain with gasless wallet updates for full provenance. Discipline: Filmmaking & Animation (scene metadata tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s social wallet and Hedera's fixed sub-cent fees remove gas barriers to continuous onchain metadata logging. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MetaScene Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-clipchain-share-19 Title: ClipChain Share Theme: Filmmaking & Animation (film-animation) · clip sharing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share animation clips instantly with embedded wallets enabling gasless transfers and social proof. Why Hedera: the embedded wallet’s wallet with Hedera's fixed sub-cent fees allows instant clip sharing without requiring user gas payments. Market: TAM $3.5B — digital clip distribution market | SAM $700M — animation clip sharing platforms | SOM $14M — blockchain clip sharing startups ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipChain Share" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share animation clips instantly with embedded wallets enabling gasless transfers and social proof. Discipline: Filmmaking & Animation (clip sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s wallet with Hedera's fixed sub-cent fees allows instant clip sharing without requiring user gas payments. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipChain Share" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animfeedback-nft-20 Title: AnimFeedback NFT Theme: Filmmaking & Animation (film-animation) · feedback monetization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Monetize animation feedback as NFTs with Magic Link email sign-ins enabling gas-free minting and transactions. Why Hedera: the embedded wallet’s embedded wallet and Hedera's fixed sub-cent fees ensure easy, gasless NFT creation for feedback tokens. Market: TAM $1B — NFT marketplace for creatives | SAM $250M — monetized feedback platforms | SOM $5M — blockchain NFT monetization ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimFeedback NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Monetize animation feedback as NFTs with Magic Link email sign-ins enabling gas-free minting and transactions. Discipline: Filmmaking & Animation (feedback monetization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s embedded wallet and Hedera's fixed sub-cent fees ensure easy, gasless NFT creation for feedback tokens. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimFeedback NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboard-mint-21 Title: Storyboard Mint Theme: Filmmaking & Animation (film-animation) · storyboard NFT minting Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint storyboards as gasless NFTs via embedded social wallets for easy ownership and transfer. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees remove minting gas barriers for creators and producers. Market: TAM $2B — digital storyboard assets | SAM $400M — NFT minting tools | SOM $8M — blockchain storyboard markets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint storyboards as gasless NFTs via embedded social wallets for easy ownership and transfer. Discipline: Filmmaking & Animation (storyboard NFT minting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees remove minting gas barriers for creators and producers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-anim-guild-22 Title: Anim Guild Theme: Filmmaking & Animation (film-animation) · community governance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create animator guilds with onchain membership and governance via gasless Magic Link email sign-in interactions. Why Hedera: the embedded wallet’s social wallet and Hedera's fixed sub-cent fees enable barrier-free participation without gas for members. Market: TAM $500M — animator professional networks | SAM $100M — creative guild SaaS | SOM $2M — blockchain community governance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Anim Guild" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create animator guilds with onchain membership and governance via gasless Magic Link email sign-in interactions. Discipline: Filmmaking & Animation (community governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s social wallet and Hedera's fixed sub-cent fees enable barrier-free participation without gas for members. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Anim Guild" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-motiontrack-rewards-23 Title: MotionTrack Rewards Theme: Filmmaking & Animation (film-animation) · performance incentives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward motion designers instantly with sponsored gasless wallet transactions tied to creative milestones. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allow seamless incentives without gas payment delays. Market: TAM $1.5B — creative incentives market | SAM $300M — freelancer reward platforms | SOM $6M — blockchain incentive tech ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionTrack Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward motion designers instantly with sponsored gasless wallet transactions tied to creative milestones. Discipline: Filmmaking & Animation (performance incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allow seamless incentives without gas payment delays. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionTrack Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-clipchain-rights-24 Title: ClipChain Rights Theme: Filmmaking & Animation (film-animation) · clip rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage animation clip rights transparently with onchain wallet transactions and gasless sponsored operations. Why Hedera: the embedded wallet’s embedded wallets with Hedera's fixed sub-cent fees remove friction from rights transfers and proofs. Market: TAM $4B — digital clip rights market | SAM $800M — rights management SaaS | SOM $18M — blockchain clip rights ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipChain Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage animation clip rights transparently with onchain wallet transactions and gasless sponsored operations. Discipline: Filmmaking & Animation (clip rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s embedded wallets with Hedera's fixed sub-cent fees remove friction from rights transfers and proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipChain Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-framechain-legacy-0 Title: FrameChain Legacy Theme: Filmmaking & Animation (film-animation) · storyboard provenance Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint unique storyboard sequences proving original authorship and evolution. Why Hedera: Immutable minting ensures incontestable ownership and version history on IPFS. Market: TAM $400B — global animation and filmmaking industry | SAM $50B — professional storyboard and previsualization tools | SOM $500M — digital storyboard NFT platforms for creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameChain Legacy" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint unique storyboard sequences proving original authorship and evolution. Discipline: Filmmaking & Animation (storyboard provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable minting ensures incontestable ownership and version history on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameChain Legacy" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animmint-vault-1 Title: AnimMint Vault Theme: Filmmaking & Animation (film-animation) · character animation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFT-verified character animations to authenticate originality and usage rights. Why Hedera: HTS NFT minting links unique animation CIDs with creator identity on-chain. Market: TAM $400B — animation market including film and TV | SAM $30B — character animation software and assets | SOM $200M — certified NFT animation asset marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimMint Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFT-verified character animations to authenticate originality and usage rights. Discipline: Filmmaking & Animation (character animation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting links unique animation CIDs with creator identity on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimMint Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-scenestamp-auth-2 Title: SceneStamp Auth Theme: Filmmaking & Animation (film-animation) · scene composition Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint entire scene compositions as NFTs to protect creative framing and direction. Why Hedera: NFT provenance solidifies scene ownership and timestamp on Hedera testnet. Market: TAM $400B — global visual storytelling industries | SAM $20B — scene design and virtual production solutions | SOM $150M — NFT-based scene licensing services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneStamp Auth" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint entire scene compositions as NFTs to protect creative framing and direction. Discipline: Filmmaking & Animation (scene composition). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance solidifies scene ownership and timestamp on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneStamp Auth" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-motionmark-ledger-3 Title: MotionMark Ledger Theme: Filmmaking & Animation (film-animation) · motion design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate motion design clips with NFT provenance for fair distribution and recognition. Why Hedera: HTS NFT tokens create secure, transferable motion design ownership proofs. Market: TAM $400B — global animation and motion design sector | SAM $25B — motion graphics software market | SOM $100M — NFT catalogs of motion design assets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionMark Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate motion design clips with NFT provenance for fair distribution and recognition. Discipline: Filmmaking & Animation (motion design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens create secure, transferable motion design ownership proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionMark Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-colorproof-chain-4 Title: ColorProof Chain Theme: Filmmaking & Animation (film-animation) · color grading Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint color grading profiles as NFTs to guarantee creative credits and prevent misuse. Why Hedera: On-chain minting anchors unique color schemas with verified creators. Market: TAM $400B — film and animation post-production | SAM $10B — color grading software and plugins | SOM $50M — color grading NFT profile marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorProof Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint color grading profiles as NFTs to guarantee creative credits and prevent misuse. Discipline: Filmmaking & Animation (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: On-chain minting anchors unique color schemas with verified creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorProof Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-vfx-provenance-5 Title: VFX Provenance Theme: Filmmaking & Animation (film-animation) · visual effects Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure VFX shots as NFTs ensuring rightful ownership and usage tracking across productions. Why Hedera: HTS NFT minting allows immutable proof of complex effect ownership. Market: TAM $400B — film and animation visual effects industry | SAM $40B — VFX software and assets market | SOM $120M — VFX NFT asset exchanges ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VFX Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure VFX shots as NFTs ensuring rightful ownership and usage tracking across productions. Discipline: Filmmaking & Animation (visual effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting allows immutable proof of complex effect ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VFX Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-propmint-archive-6 Title: PropMint Archive Theme: Filmmaking & Animation (film-animation) · 3D prop modeling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint 3D prop assets as NFTs to certify originality and control licensing. Why Hedera: NFT linkage to IPFS CIDs provides tamper-proof 3D model provenance. Market: TAM $400B — animation and filmmaking asset creation | SAM $15B — 3D modeling tools and marketplaces | SOM $80M — NFT 3D asset trading platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropMint Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint 3D prop assets as NFTs to certify originality and control licensing. Discipline: Filmmaking & Animation (3D prop modeling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT linkage to IPFS CIDs provides tamper-proof 3D model provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropMint Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-voicetrack-token-7 Title: VoiceTrack Token Theme: Filmmaking & Animation (film-animation) · voiceover recording Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate voiceover clips with provenance NFTs ensuring creator recognition and rights. Why Hedera: HTS NFT tokens anchor unique audio assets on-chain linked to creators. Market: TAM $400B — animation and film audio sectors | SAM $8B — voiceover and audio production software | SOM $30M — NFT verified voice asset exchanges ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VoiceTrack Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate voiceover clips with provenance NFTs ensuring creator recognition and rights. Discipline: Filmmaking & Animation (voiceover recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens anchor unique audio assets on-chain linked to creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VoiceTrack Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animsound-ledger-8 Title: AnimSound Ledger Theme: Filmmaking & Animation (film-animation) · sound design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint sound design elements as NFTs verifying originality and usage permissions. Why Hedera: Immutable NFT minting ensures sound asset provenance and creator rights. Market: TAM $400B — film and animation sound design markets | SAM $10B — sound design asset marketplaces | SOM $40M — NFT based sound design licensing portals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimSound Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint sound design elements as NFTs verifying originality and usage permissions. Discipline: Filmmaking & Animation (sound design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFT minting ensures sound asset provenance and creator rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimSound Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboard-chain-9 Title: Storyboard Chain Theme: Filmmaking & Animation (film-animation) · visual storytelling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure visual storyboards on-chain to track creator rights and version history. Why Hedera: HTS NFT minting with IPFS CIDs provides immutable storyboard evidence. Market: TAM $400B — global story-driven media creation | SAM $10B — digital storyboard and pre-visualization tools | SOM $25M — NFT storyboard ownership platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure visual storyboards on-chain to track creator rights and version history. Discipline: Filmmaking & Animation (visual storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting with IPFS CIDs provides immutable storyboard evidence. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animloop-provenance-10 Title: AnimLoop Provenance Theme: Filmmaking & Animation (film-animation) · animation loops Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create provenance-backed NFT loops ensuring traceable animation reuse and credit. Why Hedera: On-chain NFT minting certifies unique loop animations with creator data. Market: TAM $400B — animation and motion design industry | SAM $5B — looped animation marketplace | SOM $20M — NFT verified animation loop repositories ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimLoop Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create provenance-backed NFT loops ensuring traceable animation reuse and credit. Discipline: Filmmaking & Animation (animation loops). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: On-chain NFT minting certifies unique loop animations with creator data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimLoop Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-cuttoken-verify-11 Title: CutToken Verify Theme: Filmmaking & Animation (film-animation) · film editing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint finalized film cuts as NFTs to guarantee ownership and distribution rights. Why Hedera: HTS NFT tokens link complex edits immutably with creators on Hedera testnet. Market: TAM $400B — global film post-production | SAM $30B — video editing software and services | SOM $150M — NFT authenticated film editing platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CutToken Verify" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint finalized film cuts as NFTs to guarantee ownership and distribution rights. Discipline: Filmmaking & Animation (film editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link complex edits immutably with creators on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CutToken Verify" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboardchain-script-12 Title: StoryboardChain Script Theme: Filmmaking & Animation (film-animation) · script development Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint script drafts as NFTs to secure writing credits and version control. Why Hedera: NFT provenance on IPFS timestamps and validates original script ownership. Market: TAM $400B — film and animation scriptwriting markets | SAM $8B — digital scriptwriting platforms | SOM $15M — NFT script ownership services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryboardChain Script" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint script drafts as NFTs to secure writing credits and version control. Discipline: Filmmaking & Animation (script development). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance on IPFS timestamps and validates original script ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryboardChain Script" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-moodboard-token-13 Title: MoodBoard Token Theme: Filmmaking & Animation (film-animation) · concept art Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate concept moodboards as NFTs protecting early creative vision rights. Why Hedera: HTS NFT minting ties visual concepts to immutable on-chain records. Market: TAM $400B — animation and filmmaking concept development | SAM $12B — concept art and design tools | SOM $10M — NFT moodboard ownership platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoodBoard Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate concept moodboards as NFTs protecting early creative vision rights. Discipline: Filmmaking & Animation (concept art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting ties visual concepts to immutable on-chain records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoodBoard Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animframe-tag-14 Title: AnimFrame Tag Theme: Filmmaking & Animation (film-animation) · frame-by-frame animation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint keyframes as NFTs to prove original frame animation ownership and usage. Why Hedera: HTS NFT tokens ensure unique keyframe provenance with IPFS CID linkage. Market: TAM $400B — traditional and digital animation | SAM $20B — frame-by-frame animation software | SOM $25M — NFT keyframe asset marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimFrame Tag" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint keyframes as NFTs to prove original frame animation ownership and usage. Discipline: Filmmaking & Animation (frame-by-frame animation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens ensure unique keyframe provenance with IPFS CID linkage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimFrame Tag" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboardclip-token-15 Title: StoryboardClip Token Theme: Filmmaking & Animation (film-animation) · animatic creation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint animatic clips as NFTs to secure early-stage animated story ownership. Why Hedera: On-chain provenance verifies combined storyboard and timing clips. Market: TAM $400B — animation production pipelines | SAM $5B — animatic and previsualization tools | SOM $8M — NFT animatic ownership services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryboardClip Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint animatic clips as NFTs to secure early-stage animated story ownership. Discipline: Filmmaking & Animation (animatic creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: On-chain provenance verifies combined storyboard and timing clips. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryboardClip Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-charactermint-badge-16 Title: CharacterMint Badge Theme: Filmmaking & Animation (film-animation) · character design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate character design sheets as NFTs to establish creator credit and licensing. Why Hedera: HTS NFT minting links unique character assets with original artists on Hedera testnet. Market: TAM $400B — character-driven media and animation | SAM $25B — character art and design software | SOM $50M — NFT character design trading platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CharacterMint Badge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate character design sheets as NFTs to establish creator credit and licensing. Discipline: Filmmaking & Animation (character design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting links unique character assets with original artists on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CharacterMint Badge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animvoice-ledger-17 Title: AnimVoice Ledger Theme: Filmmaking & Animation (film-animation) · voice acting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint voice acting performances as NFTs proving rights and enabling royalty tracking. Why Hedera: HTS NFT tokens anchor audio with creator identity on blockchain. Market: TAM $400B — animated film and TV voice talent markets | SAM $12B — voice recording and distribution tools | SOM $25M — NFT voice performance registries ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimVoice Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint voice acting performances as NFTs proving rights and enabling royalty tracking. Discipline: Filmmaking & Animation (voice acting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens anchor audio with creator identity on blockchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimVoice Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-propchain-license-18 Title: PropChain License Theme: Filmmaking & Animation (film-animation) · virtual prop rental Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint rental licenses as NFTs for 3D props enabling secure temporary usage. Why Hedera: NFT provenance allows traceable licenses and ownership transfers on Hedera testnet. Market: TAM $400B — filmmaking virtual asset rentals | SAM $5B — 3D prop rental services | SOM $10M — NFT powered prop rental platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropChain License" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint rental licenses as NFTs for 3D props enabling secure temporary usage. Discipline: Filmmaking & Animation (virtual prop rental). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance allows traceable licenses and ownership transfers on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropChain License" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animtext-token-19 Title: AnimText Token Theme: Filmmaking & Animation (film-animation) · animated typography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint animated typography clips as NFTs to secure originality and distribution rights. Why Hedera: HTS NFT minting ensures unique typographic animation ownership linked on-chain. Market: TAM $400B — animated content and design market | SAM $3B — animated text software tools | SOM $8M — NFT animated typography platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimText Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint animated typography clips as NFTs to secure originality and distribution rights. Discipline: Filmmaking & Animation (animated typography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting ensures unique typographic animation ownership linked on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimText Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-sceneaccess-pass-20 Title: SceneAccess Pass Theme: Filmmaking & Animation (film-animation) · virtual production Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFT passes granting creator access to virtual production scene data and assets. Why Hedera: HTS NFT tokens securely regulate and verify access rights on Hedera testnet. Market: TAM $400B — virtual production and filmmaking tech | SAM $15B — virtual production platforms | SOM $5M — NFT gated scene access solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneAccess Pass" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFT passes granting creator access to virtual production scene data and assets. Discipline: Filmmaking & Animation (virtual production). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens securely regulate and verify access rights on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneAccess Pass" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animsketch-token-21 Title: AnimSketch Token Theme: Filmmaking & Animation (film-animation) · animator sketchbooks Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint animator sketchbooks as NFTs to prove original creative process ownership. Why Hedera: HTS NFT minting archives unique sketches immutably on IPFS with creator identity. Market: TAM $400B — animation industry creative workflows | SAM $7B — digital sketch and drawing tools | SOM $3M — NFT animator process archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimSketch Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint animator sketchbooks as NFTs to prove original creative process ownership. Discipline: Filmmaking & Animation (animator sketchbooks). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting archives unique sketches immutably on IPFS with creator identity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimSketch Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-storyboardsync-ledger-22 Title: StoryboardSync Ledger Theme: Filmmaking & Animation (film-animation) · collaborative storyboarding Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFT storyboards to track collaborative edits and contributions transparently. Why Hedera: HTS NFT tokens provide auditable provenance on multi-contributor storyboards. Market: TAM $400B — collaborative animation and film creation | SAM $5B — digital collaboration storyboard tools | SOM $12M — NFT based collaborative ownership platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryboardSync Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFT storyboards to track collaborative edits and contributions transparently. Discipline: Filmmaking & Animation (collaborative storyboarding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide auditable provenance on multi-contributor storyboards. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryboardSync Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animpitch-token-23 Title: AnimPitch Token Theme: Filmmaking & Animation (film-animation) · project pitching Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFT pitch decks for animation projects guaranteeing originality and creator rights. Why Hedera: HTS NFT minting provides immutable pitch asset authentication on-chain. Market: TAM $400B — animation project financing and pitching | SAM $4B — digital pitch and proposal tools | SOM $2M — NFT verified animation pitches ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimPitch Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFT pitch decks for animation projects guaranteeing originality and creator rights. Discipline: Filmmaking & Animation (project pitching). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting provides immutable pitch asset authentication on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimPitch Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA film-animation-animcycle-provenance-24 Title: AnimCycle Provenance Theme: Filmmaking & Animation (film-animation) · loop cycles Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFT walk and run cycles to protect proprietary animation loops and licensing. Why Hedera: HTS NFT tokens secure unique cycle animations with creator provenance on Hedera testnet. Market: TAM $400B — animation lifecycle asset market | SAM $3B — animation loop asset marketplaces | SOM $7M — NFT loop animation ownership platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AnimCycle Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFT walk and run cycles to protect proprietary animation loops and licensing. Discipline: Filmmaking & Animation (loop cycles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens secure unique cycle animations with creator provenance on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AnimCycle Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Game Design & Interactive Media game designers, interactive artists, XR creators ============================================================================== ------------------------------------------------------------------------------ IDEA games-onchain-quest-ledger-0 Title: Onchain Quest Ledger Theme: Game Design & Interactive Media (games) · quest tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely log player quest progress and achievements on Hedera testnet to prove in-game milestones. Why Hedera: Hedera testnet smart contracts enable tamper-proof, transparent quest data recording accessible to all parties. Market: TAM $1.2B — global RPG market valuing player progression features | SAM $300M — blockchain-enhanced gaming platforms | SOM $25M — indie RPG developers integrating onchain tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Quest Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely log player quest progress and achievements on Hedera testnet to prove in-game milestones. Discipline: Game Design & Interactive Media (quest tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable tamper-proof, transparent quest data recording accessible to all parties. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Quest Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-tokenized-loot-drops-1 Title: Tokenized Loot Drops Theme: Game Design & Interactive Media (games) · item distribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Distribute rare, verifiable loot as tokens directly through Hedera testnet smart contracts to guarantee authenticity. Why Hedera: Hedera testnet allows automated, transparent item drops triggered by onchain game events. Market: TAM $2.5B — global loot box and item trading market | SAM $800M — NFT-powered gaming assets | SOM $40M — indie multiplayer games with tokenized economies ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Loot Drops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute rare, verifiable loot as tokens directly through Hedera testnet smart contracts to guarantee authenticity. Discipline: Game Design & Interactive Media (item distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet allows automated, transparent item drops triggered by onchain game events. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Loot Drops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-player-reputation-ledger-2 Title: Player Reputation Ledger Theme: Game Design & Interactive Media (games) · community trust Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create immutable player reputation scores recorded on Hedera testnet to foster fair matchmaking and collaboration. Why Hedera: Hedera testnet smart contracts secure trust data on an open, censorship-resistant platform. Market: TAM $900M — global online multiplayer market needing trust systems | SAM $200M — blockchain reputation management in games | SOM $15M — indie developers building social trust features ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Player Reputation Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create immutable player reputation scores recorded on Hedera testnet to foster fair matchmaking and collaboration. Discipline: Game Design & Interactive Media (community trust). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts secure trust data on an open, censorship-resistant platform. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Player Reputation Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-puzzle-locks-3 Title: Onchain Puzzle Locks Theme: Game Design & Interactive Media (games) · interactive puzzles Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Design puzzles whose solutions must be proven onchain via Hedera testnet smart contracts to unlock game content. Why Hedera: Hedera testnet ensures unique, verifiable puzzle solution submissions maintained transparently. Market: TAM $700M — global puzzle game revenue including ARGs | SAM $180M — blockchain-integrated puzzle experiences | SOM $12M — indie puzzle creators exploring cryptographic challenges ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Puzzle Locks" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Design puzzles whose solutions must be proven onchain via Hedera testnet smart contracts to unlock game content. Discipline: Game Design & Interactive Media (interactive puzzles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet ensures unique, verifiable puzzle solution submissions maintained transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Puzzle Locks" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-immutable-scoreboards-4 Title: Immutable Scoreboards Theme: Game Design & Interactive Media (games) · competitive scoring Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Store game scores on Hedera testnet to prevent cheating and create permanent leaderboards. Why Hedera: Hedera testnet smart contracts guarantee score tamper-resistance and verifiable competition. Market: TAM $1.1B — esports and competitive gaming markets | SAM $350M — blockchain-based scoreboard solutions | SOM $22M — indie competitive game developers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Immutable Scoreboards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store game scores on Hedera testnet to prevent cheating and create permanent leaderboards. Discipline: Game Design & Interactive Media (competitive scoring). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts guarantee score tamper-resistance and verifiable competition. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Immutable Scoreboards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-collectible-avatars-5 Title: Collectible Avatars Theme: Game Design & Interactive Media (games) · character customization Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint avatar parts as unique tokens on Hedera testnet, enabling player-owned, tradable character features. Why Hedera: Hedera testnet supports unique ownership and provenance tracking for avatar elements. Market: TAM $3B — global avatar accessory market | SAM $1B — blockchain-customizable in-game assets | SOM $60M — indie games specializing in avatar economies ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collectible Avatars" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint avatar parts as unique tokens on Hedera testnet, enabling player-owned, tradable character features. Discipline: Game Design & Interactive Media (character customization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet supports unique ownership and provenance tracking for avatar elements. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collectible Avatars" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-narrative-branches-6 Title: Onchain Narrative Branches Theme: Game Design & Interactive Media (games) · storytelling Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record player story choices on Hedera testnet to create transparent, shared branching narratives. Why Hedera: Hedera testnet offers immutable audit trails for branching decisions enhancing shared story experiences. Market: TAM $900M — interactive story-driven games market | SAM $250M — blockchain-enabled narrative games | SOM $18M — indie narrative game developers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Narrative Branches" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record player story choices on Hedera testnet to create transparent, shared branching narratives. Discipline: Game Design & Interactive Media (storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet offers immutable audit trails for branching decisions enhancing shared story experiences. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Narrative Branches" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-decentralized-game-voting-7 Title: Decentralized Game Voting Theme: Game Design & Interactive Media (games) · community governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Empower players to vote on game decisions using Hedera testnet smart contracts for transparent results. Why Hedera: Hedera testnet ensures secure, verifiable voting with onchain tallying and auditability. Market: TAM $500M — player-driven game feature development market | SAM $150M — blockchain governance in games | SOM $10M — indie developers using onchain voting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Game Voting" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Empower players to vote on game decisions using Hedera testnet smart contracts for transparent results. Discipline: Game Design & Interactive Media (community governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet ensures secure, verifiable voting with onchain tallying and auditability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Game Voting" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-proof-of-play-events-8 Title: Proof-of-Play Events Theme: Game Design & Interactive Media (games) · event verification Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Authenticate player participation in timed events on Hedera testnet for exclusive rewards and recognition. Why Hedera: Hedera testnet smart contracts provide immutable proof of player interaction and eligibility. Market: TAM $450M — global online event gaming market | SAM $120M — blockchain-based event verification | SOM $8M — indie event-centric games ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Proof-of-Play Events" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate player participation in timed events on Hedera testnet for exclusive rewards and recognition. Discipline: Game Design & Interactive Media (event verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide immutable proof of player interaction and eligibility. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Proof-of-Play Events" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-multiplayer-matchmaking-9 Title: Onchain Multiplayer Matchmaking Theme: Game Design & Interactive Media (games) · match coordination Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Use Hedera testnet to transparently pair players based on verified criteria ensuring fairness. Why Hedera: Hedera testnet smart contracts enable decentralized, tamper-proof matchmaking logic. Market: TAM $2B — global online multiplayer market | SAM $600M — blockchain-enhanced matchmaking solutions | SOM $30M — indie multiplayer developers exploring onchain matching ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Multiplayer Matchmaking" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use Hedera testnet to transparently pair players based on verified criteria ensuring fairness. Discipline: Game Design & Interactive Media (match coordination). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable decentralized, tamper-proof matchmaking logic. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Multiplayer Matchmaking" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-rare-achievement-tokens-10 Title: Rare Achievement Tokens Theme: Game Design & Interactive Media (games) · achievement systems Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint unique achievement tokens on Hedera testnet when players hit milestones to prove in-game skills. Why Hedera: Hedera testnet supports unique, traceable achievement token issuance tied to player actions. Market: TAM $1B — game achievements and rewards market | SAM $400M — blockchain achievement token markets | SOM $25M — indie developers integrating unique achievements ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Rare Achievement Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique achievement tokens on Hedera testnet when players hit milestones to prove in-game skills. Discipline: Game Design & Interactive Media (achievement systems). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet supports unique, traceable achievement token issuance tied to player actions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Rare Achievement Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-virtual-economies-11 Title: Onchain Virtual Economies Theme: Game Design & Interactive Media (games) · economy management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage in-game currency and item economies via Hedera testnet smart contracts for secure, transparent transactions. Why Hedera: Hedera testnet provides decentralized control and auditability of virtual economy flows. Market: TAM $5B — global in-game economy market | SAM $1.5B — blockchain virtual economy platforms | SOM $75M — indie developers building decentralized economies ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Virtual Economies" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage in-game currency and item economies via Hedera testnet smart contracts for secure, transparent transactions. Discipline: Game Design & Interactive Media (economy management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet provides decentralized control and auditability of virtual economy flows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Virtual Economies" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-dynamic-nft-game-assets-12 Title: Dynamic NFT Game Assets Theme: Game Design & Interactive Media (games) · asset evolution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Allow game assets to evolve on Hedera testnet based on gameplay, creating unique player-owned NFTs. Why Hedera: Hedera testnet smart contracts enable programmable, state-changing blockchain assets. Market: TAM $2B — NFT gaming assets market | SAM $700M — dynamic NFT implementations | SOM $35M — indie NFT game studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dynamic NFT Game Assets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Allow game assets to evolve on Hedera testnet based on gameplay, creating unique player-owned NFTs. Discipline: Game Design & Interactive Media (asset evolution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable programmable, state-changing blockchain assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dynamic NFT Game Assets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-scavenger-hunts-13 Title: Onchain Scavenger Hunts Theme: Game Design & Interactive Media (games) · interactive exploration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create geolocated scavenger hunts tracked and verified on Hedera testnet to reward player discoveries. Why Hedera: Hedera testnet supports timestamped, location-proof recording via smart contracts. Market: TAM $600M — augmented reality game market | SAM $180M — blockchain location-based games | SOM $15M — indie AR game creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Scavenger Hunts" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create geolocated scavenger hunts tracked and verified on Hedera testnet to reward player discoveries. Discipline: Game Design & Interactive Media (interactive exploration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet supports timestamped, location-proof recording via smart contracts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Scavenger Hunts" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-transparent-rng-mechanics-14 Title: Transparent RNG Mechanics Theme: Game Design & Interactive Media (games) · game fairness Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Implement verifiable random number generation on Hedera testnet to ensure fair game outcomes. Why Hedera: Hedera testnet smart contracts provide unbiased, publicly verifiable randomness sources. Market: TAM $1.3B — global gaming fairness and anti-cheat market | SAM $400M — blockchain RNG services | SOM $28M — indie developers needing trustworthy RNG ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Transparent RNG Mechanics" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Implement verifiable random number generation on Hedera testnet to ensure fair game outcomes. Discipline: Game Design & Interactive Media (game fairness). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide unbiased, publicly verifiable randomness sources. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Transparent RNG Mechanics" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-skill-trees-15 Title: Onchain Skill Trees Theme: Game Design & Interactive Media (games) · character progression Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record and validate player skill tree choices on Hedera testnet ensuring unique character builds are guaranteed. Why Hedera: Hedera testnet smart contracts maintain immutable records of progression states. Market: TAM $1B — skill progression system market | SAM $350M — blockchain-based progression tracking | SOM $20M — indie RPG and strategy developers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Skill Trees" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and validate player skill tree choices on Hedera testnet ensuring unique character builds are guaranteed. Discipline: Game Design & Interactive Media (character progression). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts maintain immutable records of progression states. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Skill Trees" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-decentralized-story-co-creation-16 Title: Decentralized Story Co-Creation Theme: Game Design & Interactive Media (games) · collaborative narrative Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Facilitate community-driven story creation by recording contributions on Hedera testnet for transparent authorship. Why Hedera: Hedera testnet enables tamper-proof tracking of collaborative input and ownership rights. Market: TAM $800M — interactive storytelling market | SAM $220M — blockchain narrative collaboration | SOM $14M — indie interactive media creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Story Co-Creation" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate community-driven story creation by recording contributions on Hedera testnet for transparent authorship. Discipline: Game Design & Interactive Media (collaborative narrative). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet enables tamper-proof tracking of collaborative input and ownership rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Story Co-Creation" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-game-jam-scores-17 Title: Onchain Game Jam Scores Theme: Game Design & Interactive Media (games) · competition scoring Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Publish and verify game jam scores on Hedera testnet to ensure fairness and public recognition. Why Hedera: Hedera testnet smart contracts provide immutable score records for competitive transparency. Market: TAM $400M — game jam and indie competition market | SAM $90M — blockchain-based competition platforms | SOM $7M — indie game jam organizers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Game Jam Scores" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Publish and verify game jam scores on Hedera testnet to ensure fairness and public recognition. Discipline: Game Design & Interactive Media (competition scoring). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide immutable score records for competitive transparency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Game Jam Scores" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-player-created-onchain-items-18 Title: Player-Created Onchain Items Theme: Game Design & Interactive Media (games) · user-generated content Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Allow players to mint and trade custom items on Hedera testnet smart contracts within games. Why Hedera: Hedera testnet's verified contracts facilitate trust in player-created asset ownership. Market: TAM $1.7B — user-generated content monetization | SAM $500M — blockchain-based content marketplaces | SOM $30M — indie games with UGC economies ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Player-Created Onchain Items" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Allow players to mint and trade custom items on Hedera testnet smart contracts within games. Discipline: Game Design & Interactive Media (user-generated content). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet's verified contracts facilitate trust in player-created asset ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Player-Created Onchain Items" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-vr-experience-logs-19 Title: Onchain VR Experience Logs Theme: Game Design & Interactive Media (games) · immersive interaction Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record user interactions in VR on Hedera testnet for secure session histories and experience sharing. Why Hedera: Hedera testnet contracts ensure unalterable onchain interaction records for immersive experiences. Market: TAM $1.5B — VR software and content market | SAM $450M — blockchain-enhanced VR applications | SOM $25M — indie VR creators needing trustable logs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain VR Experience Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record user interactions in VR on Hedera testnet for secure session histories and experience sharing. Discipline: Game Design & Interactive Media (immersive interaction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts ensure unalterable onchain interaction records for immersive experiences. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain VR Experience Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-blockchain-dialogue-trees-20 Title: Blockchain Dialogue Trees Theme: Game Design & Interactive Media (games) · interactive dialogue Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Store branching dialogue choices on Hedera testnet to enable transparent player-driven storytelling. Why Hedera: Hedera testnet smart contracts maintain an immutable record of dialogue paths taken. Market: TAM $850M — interactive narrative dialogue market | SAM $300M — blockchain dialogue integration | SOM $16M — indie narrative game developers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Blockchain Dialogue Trees" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store branching dialogue choices on Hedera testnet to enable transparent player-driven storytelling. Discipline: Game Design & Interactive Media (interactive dialogue). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts maintain an immutable record of dialogue paths taken. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Blockchain Dialogue Trees" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-speedrun-verification-21 Title: Onchain Speedrun Verification Theme: Game Design & Interactive Media (games) · challenge validation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Verify and timestamp speedrun attempts on Hedera testnet smart contracts to prevent cheating. Why Hedera: Hedera testnet provides immutable, verifiable timestamping for performance validation. Market: TAM $600M — speedrunning and challenge gaming market | SAM $150M — blockchain-based verification services | SOM $9M — indie speedrun community tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Speedrun Verification" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify and timestamp speedrun attempts on Hedera testnet smart contracts to prevent cheating. Discipline: Game Design & Interactive Media (challenge validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet provides immutable, verifiable timestamping for performance validation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Speedrun Verification" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-decentralized-game-mods-22 Title: Decentralized Game Mods Theme: Game Design & Interactive Media (games) · mod distribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Distribute and verify mods using Hedera testnet smart contracts for secure, community-driven content sharing. Why Hedera: Hedera testnet enables verifiable ownership and integrity checks for mod files. Market: TAM $1.1B — game modding market | SAM $350M — blockchain-based content distribution | SOM $22M — indie modding tool developers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Game Mods" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute and verify mods using Hedera testnet smart contracts for secure, community-driven content sharing. Discipline: Game Design & Interactive Media (mod distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet enables verifiable ownership and integrity checks for mod files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Game Mods" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-token-gated-game-access-23 Title: Token-Gated Game Access Theme: Game Design & Interactive Media (games) · access control Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Use Hedera testnet smart contracts to restrict game content access via ownership of specific tokens. Why Hedera: Hedera testnet enforces secure token-based permissions directly onchain. Market: TAM $2.2B — premium and gated game content market | SAM $700M — blockchain access control solutions | SOM $40M — indie developers experimenting with token gating ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Token-Gated Game Access" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use Hedera testnet smart contracts to restrict game content access via ownership of specific tokens. Discipline: Game Design & Interactive Media (access control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet enforces secure token-based permissions directly onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Token-Gated Game Access" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-interactive-artpieces-24 Title: Onchain Interactive Artpieces Theme: Game Design & Interactive Media (games) · generative art Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create interactive artworks whose states and ownership are controlled via Hedera testnet smart contracts. Why Hedera: Hedera testnet supports programmable art with provable provenance and interaction logs. Market: TAM $600M — digital and interactive art market | SAM $200M — blockchain-enabled generative art | SOM $12M — indie interactive media artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Interactive Artpieces" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create interactive artworks whose states and ownership are controlled via Hedera testnet smart contracts. Discipline: Game Design & Interactive Media (generative art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet supports programmable art with provable provenance and interaction logs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Interactive Artpieces" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-pixel-lore-vault-0 Title: Pixel Lore Vault Theme: Game Design & Interactive Media (games) · game narrative archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently store and share evolving game storylines and lore across player communities. Why Hedera: Pinata IPFS ensures immutable, permanent lore archives accessible worldwide without centralized servers. Market: TAM $1B — narrative tools for game studios globally | SAM $200M — indie narrative design tools market | SOM $10M — early adopters in community-driven storytelling ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pixel Lore Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently store and share evolving game storylines and lore across player communities. Discipline: Game Design & Interactive Media (game narrative archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS ensures immutable, permanent lore archives accessible worldwide without centralized servers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pixel Lore Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-avatar-trait-forge-1 Title: Avatar Trait Forge Theme: Game Design & Interactive Media (games) · character customization data Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Save and share unique character traits and skins securely and permanently on IPFS. Why Hedera: Pinning character assets to IPFS guarantees permanent player ownership and access. Market: TAM $3B — avatar customization market in gaming | SAM $400M — indie avatar tool users | SOM $25M — avatar trait NFT collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Avatar Trait Forge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save and share unique character traits and skins securely and permanently on IPFS. Discipline: Game Design & Interactive Media (character customization data). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning character assets to IPFS guarantees permanent player ownership and access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Avatar Trait Forge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-quest-chain-archive-2 Title: Quest Chain Archive Theme: Game Design & Interactive Media (games) · interactive quest design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Distribute permanent quest data and decision trees for replayable and modifiable game adventures. Why Hedera: IPFS via Pinata preserves quest data immutably, enabling decentralized quest sharing. Market: TAM $800M — global interactive quest tools | SAM $120M — indie game narrative tools | SOM $8M — experimental quest designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Quest Chain Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute permanent quest data and decision trees for replayable and modifiable game adventures. Discipline: Game Design & Interactive Media (interactive quest design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata preserves quest data immutably, enabling decentralized quest sharing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Quest Chain Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-xr-scene-snapshot-3 Title: XR Scene Snapshot Theme: Game Design & Interactive Media (games) · extended reality content storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin immersive XR scenes and metadata permanently for cross-platform sharing and reuse. Why Hedera: IPFS guarantees persistent XR assets unbound by platform constraints or server downtime. Market: TAM $2B — XR content creation software | SAM $350M — indie XR artists | SOM $20M — XR scene archivists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "XR Scene Snapshot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin immersive XR scenes and metadata permanently for cross-platform sharing and reuse. Discipline: Game Design & Interactive Media (extended reality content storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees persistent XR assets unbound by platform constraints or server downtime. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "XR Scene Snapshot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-procedural-map-depository-4 Title: Procedural Map Depository Theme: Game Design & Interactive Media (games) · map generation and sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely archive and share procedurally generated game maps for community use and collaboration. Why Hedera: Pinata IPFS enables permanent decentralized access to dynamic map datasets. Market: TAM $1.2B — game level design tools | SAM $250M — indie map creators | SOM $15M — collaborative map builders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Procedural Map Depository" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely archive and share procedurally generated game maps for community use and collaboration. Discipline: Game Design & Interactive Media (map generation and sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS enables permanent decentralized access to dynamic map datasets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Procedural Map Depository" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-artifact-provenance-ledger-5 Title: Artifact Provenance Ledger Theme: Game Design & Interactive Media (games) · in-game item history tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track and preserve full history of rare game artifacts immutably for authentic ownership proof. Why Hedera: Pinning JSON manifests to IPFS ensures permanent, trustless provenance data storage. Market: TAM $4B — virtual item marketplaces | SAM $500M — indie collectible creators | SOM $35M — artifact provenance enthusiasts ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Artifact Provenance Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and preserve full history of rare game artifacts immutably for authentic ownership proof. Discipline: Game Design & Interactive Media (in-game item history tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning JSON manifests to IPFS ensures permanent, trustless provenance data storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Artifact Provenance Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-audio-loop-library-6 Title: Audio Loop Library Theme: Game Design & Interactive Media (games) · sound asset curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Curate and permanently store unique game audio loops for easy reuse and remixing. Why Hedera: IPFS storage via Pinata preserves immutable high-quality audio assets accessible globally. Market: TAM $900M — game audio markets worldwide | SAM $180M — indie audio designers | SOM $12M — loop remixers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Audio Loop Library" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Curate and permanently store unique game audio loops for easy reuse and remixing. Discipline: Game Design & Interactive Media (sound asset curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS storage via Pinata preserves immutable high-quality audio assets accessible globally. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Audio Loop Library" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-interactive-comic-archive-7 Title: Interactive Comic Archive Theme: Game Design & Interactive Media (games) · visual storytelling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin interactive comic panels and scripts securely to enable decentralized reader-driven narratives. Why Hedera: IPFS provides permanent decentralized hosting for branching comic content and metadata. Market: TAM $600M — interactive storytelling market | SAM $130M — indie comic creators | SOM $7M — narrative experimenters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Comic Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin interactive comic panels and scripts securely to enable decentralized reader-driven narratives. Discipline: Game Design & Interactive Media (visual storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides permanent decentralized hosting for branching comic content and metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Comic Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-ai-npc-memory-hub-8 Title: AI NPC Memory Hub Theme: Game Design & Interactive Media (games) · non-player character data Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store AI NPC dialogue and memory states permanently for persistent, evolving game worlds. Why Hedera: Pinata IPFS ensures NPC data persistence, enabling decentralized world state consistency. Market: TAM $1.5B — AI-driven game content | SAM $300M — indie AI game devs | SOM $18M — persistent world builders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AI NPC Memory Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store AI NPC dialogue and memory states permanently for persistent, evolving game worlds. Discipline: Game Design & Interactive Media (non-player character data). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS ensures NPC data persistence, enabling decentralized world state consistency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AI NPC Memory Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-glyph-pattern-codex-9 Title: Glyph Pattern Codex Theme: Game Design & Interactive Media (games) · symbol and icon design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Archive unique glyphs and icon sets on IPFS for universal use in games and media. Why Hedera: Permanent CID storage enables universal referencing of symbol assets without centralized control. Market: TAM $400M — UI/UX design assets | SAM $90M — indie icon artists | SOM $6M — symbol reuse communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Glyph Pattern Codex" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive unique glyphs and icon sets on IPFS for universal use in games and media. Discipline: Game Design & Interactive Media (symbol and icon design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Permanent CID storage enables universal referencing of symbol assets without centralized control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Glyph Pattern Codex" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-mod-manifest-vault-10 Title: Mod Manifest Vault Theme: Game Design & Interactive Media (games) · game modification metadata Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and share mod manifests permanently to enable decentralized mod discovery and trust. Why Hedera: IPFS guarantees long-term availability and integrity of mod metadata across platforms. Market: TAM $1B — game modding ecosystem | SAM $220M — indie mod developers | SOM $14M — mod distribution networks ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Mod Manifest Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and share mod manifests permanently to enable decentralized mod discovery and trust. Discipline: Game Design & Interactive Media (game modification metadata). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees long-term availability and integrity of mod metadata across platforms. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Mod Manifest Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-dynamic-ui-blueprint-11 Title: Dynamic UI Blueprint Theme: Game Design & Interactive Media (games) · interface state storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Save and distribute dynamic game UI states and settings with permanent accessibility. Why Hedera: Pinning JSON UI states to IPFS ensures seamless cross-device interface consistency. Market: TAM $700M — game UI tool market | SAM $150M — indie UI designers | SOM $9M — dynamic interface users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dynamic UI Blueprint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save and distribute dynamic game UI states and settings with permanent accessibility. Discipline: Game Design & Interactive Media (interface state storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning JSON UI states to IPFS ensures seamless cross-device interface consistency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dynamic UI Blueprint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-lore-collaboration-board-12 Title: Lore Collaboration Board Theme: Game Design & Interactive Media (games) · community storytelling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Enable community-driven lore contributions to be pinned permanently, ensuring collective narrative ownership. Why Hedera: IPFS immutably stores community content, preventing censorship or loss. Market: TAM $500M — collaborative content platforms | SAM $100M — indie lore builders | SOM $5M — fan-driven stories ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Lore Collaboration Board" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable community-driven lore contributions to be pinned permanently, ensuring collective narrative ownership. Discipline: Game Design & Interactive Media (community storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS immutably stores community content, preventing censorship or loss. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Lore Collaboration Board" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-playable-prop-archive-13 Title: Playable Prop Archive Theme: Game Design & Interactive Media (games) · asset permanence Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently store props and interactive assets for reusable, verifiable game object libraries. Why Hedera: IPFS via Pinata ensures assets persist beyond single game lifecycles securely. Market: TAM $2B — 3D asset marketplace | SAM $400M — indie asset creators | SOM $28M — reusable prop users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playable Prop Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently store props and interactive assets for reusable, verifiable game object libraries. Discipline: Game Design & Interactive Media (asset permanence). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata ensures assets persist beyond single game lifecycles securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playable Prop Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-speedrun-data-ledger-14 Title: Speedrun Data Ledger Theme: Game Design & Interactive Media (games) · game performance recording Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin detailed speedrun metadata and replay JSONs permanently for competitive integrity and sharing. Why Hedera: IPFS guarantees immutable speedrun proof and replay data for transparency. Market: TAM $300M — esports and speedrunning communities | SAM $50M — indie speedrun organizers | SOM $3M — verified leaderboard users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Speedrun Data Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin detailed speedrun metadata and replay JSONs permanently for competitive integrity and sharing. Discipline: Game Design & Interactive Media (game performance recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees immutable speedrun proof and replay data for transparency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Speedrun Data Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-virtual-stage-archives-15 Title: Virtual Stage Archives Theme: Game Design & Interactive Media (games) · interactive performance spaces Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and preserve virtual stage designs and performance manifests for XR and live events. Why Hedera: Pinata IPFS provides persistent, decentralized access to ephemeral virtual event data. Market: TAM $1.8B — virtual events and XR | SAM $320M — indie event creators | SOM $22M — archived stage designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Stage Archives" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and preserve virtual stage designs and performance manifests for XR and live events. Discipline: Game Design & Interactive Media (interactive performance spaces). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS provides persistent, decentralized access to ephemeral virtual event data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Stage Archives" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-emotion-filter-pack-16 Title: Emotion Filter Pack Theme: Game Design & Interactive Media (games) · visual effect assets Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and share unique emotion-driven visual filters permanently for character and scene enhancement. Why Hedera: Permanent IPFS storage ensures visual effects remain accessible despite platform changes. Market: TAM $600M — game visual effects market | SAM $140M — indie effect designers | SOM $9M — emotional filter users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Emotion Filter Pack" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and share unique emotion-driven visual filters permanently for character and scene enhancement. Discipline: Game Design & Interactive Media (visual effect assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Permanent IPFS storage ensures visual effects remain accessible despite platform changes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Emotion Filter Pack" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-puzzle-logic-cache-17 Title: Puzzle Logic Cache Theme: Game Design & Interactive Media (games) · game mechanic storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store complex puzzle logic and solution paths immutably for community puzzle sharing and remixing. Why Hedera: Immutable IPFS storage prevents tampering with puzzle mechanics, ensuring fair play. Market: TAM $400M — casual puzzle games market | SAM $85M — indie puzzle creators | SOM $6M — puzzle sharing communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Puzzle Logic Cache" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store complex puzzle logic and solution paths immutably for community puzzle sharing and remixing. Discipline: Game Design & Interactive Media (game mechanic storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable IPFS storage prevents tampering with puzzle mechanics, ensuring fair play. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Puzzle Logic Cache" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-lore-token-index-18 Title: Lore Token Index Theme: Game Design & Interactive Media (games) · metadata indexing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create permanent indexes linking lore tokens to their corresponding IPFS-hosted metadata manifests. Why Hedera: Pinata IPFS enables verifiable, permanent linkage between game tokens and their lore data. Market: TAM $3B — game NFT ecosystems | SAM $450M — indie token creators | SOM $30M — lore-token collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Lore Token Index" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create permanent indexes linking lore tokens to their corresponding IPFS-hosted metadata manifests. Discipline: Game Design & Interactive Media (metadata indexing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS enables verifiable, permanent linkage between game tokens and their lore data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Lore Token Index" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-multiplayer-mod-sync-19 Title: Multiplayer Mod Sync Theme: Game Design & Interactive Media (games) · cross-user asset distribution Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin mods and game asset manifests to IPFS for real-time multiplayer mod synchronization. Why Hedera: IPFS provides distributed, permanent access to synchronize assets across player nodes. Market: TAM $1.5B — multiplayer game tools | SAM $350M — indie multiplayer devs | SOM $25M — synchronized mod users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Multiplayer Mod Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin mods and game asset manifests to IPFS for real-time multiplayer mod synchronization. Discipline: Game Design & Interactive Media (cross-user asset distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides distributed, permanent access to synchronize assets across player nodes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Multiplayer Mod Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-interactive-soundscapes-20 Title: Interactive Soundscapes Theme: Game Design & Interactive Media (games) · ambient audio curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin rich, layered ambient soundscapes permanently for immersive and customizable game atmospheres. Why Hedera: IPFS guarantees persistent access to complex audio compositions for game environments. Market: TAM $700M — game audio ambient market | SAM $170M — indie sound designers | SOM $11M — immersive audio users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Soundscapes" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin rich, layered ambient soundscapes permanently for immersive and customizable game atmospheres. Discipline: Game Design & Interactive Media (ambient audio curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS guarantees persistent access to complex audio compositions for game environments. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Soundscapes" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-immersive-narrative-nodes-21 Title: Immersive Narrative Nodes Theme: Game Design & Interactive Media (games) · branching story data Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store branching narrative nodes permanently to enable expansive, decentralized interactive storytelling. Why Hedera: IPFS ensures permanent availability of narrative node JSONs for collaborative story ecosystems. Market: TAM $900M — interactive fiction market | SAM $200M — indie narrative devs | SOM $13M — branching story authors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Immersive Narrative Nodes" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store branching narrative nodes permanently to enable expansive, decentralized interactive storytelling. Discipline: Game Design & Interactive Media (branching story data). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures permanent availability of narrative node JSONs for collaborative story ecosystems. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Immersive Narrative Nodes" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-xr-artifact-library-22 Title: XR Artifact Library Theme: Game Design & Interactive Media (games) · 3D object preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Archive and share 3D XR artifacts via IPFS, ensuring permanent access to virtual museum pieces. Why Hedera: Pinning 3D assets to IPFS offers decentralized, long-term storage beyond platform lifespans. Market: TAM $1.5B — XR content archive market | SAM $300M — indie XR artists | SOM $20M — virtual artifact curators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "XR Artifact Library" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive and share 3D XR artifacts via IPFS, ensuring permanent access to virtual museum pieces. Discipline: Game Design & Interactive Media (3D object preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning 3D assets to IPFS offers decentralized, long-term storage beyond platform lifespans. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "XR Artifact Library" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-game-jam-showcase-23 Title: Game Jam Showcase Theme: Game Design & Interactive Media (games) · project preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently pin game jam project assets and manifests to showcase creative works indefinitely. Why Hedera: IPFS preserves ephemeral jam projects beyond event timelines reliably and openly. Market: TAM $500M — game jam ecosystem | SAM $120M — indie participants | SOM $7M — showcased projects ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Game Jam Showcase" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently pin game jam project assets and manifests to showcase creative works indefinitely. Discipline: Game Design & Interactive Media (project preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS preserves ephemeral jam projects beyond event timelines reliably and openly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Game Jam Showcase" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-tokenized-lore-boards-24 Title: Tokenized Lore Boards Theme: Game Design & Interactive Media (games) · community content curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create permanent, token-linked lore boards for collaborative and credentialed story creation. Why Hedera: Pinata IPFS links lore content immutably to tokens, ensuring provenance and community edits. Market: TAM $1.3B — tokenized creative platforms | SAM $320M — indie storytellers | SOM $18M — collaborative lore communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Lore Boards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create permanent, token-linked lore boards for collaborative and credentialed story creation. Discipline: Game Design & Interactive Media (community content curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS links lore content immutably to tokens, ensuring provenance and community edits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Lore Boards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gasless-guilds-0 Title: Gasless Guilds Theme: Game Design & Interactive Media (games) · multiplayer coordination Hedera hook: Magic Link email wallet [wallet UX] Pitch: Seamlessly create and join guilds with gas-free onboarding and instant member transactions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables gasless social wallet integration, vital for frictionless group formation. Market: TAM $8B — global MMOG social features market | SAM $1.5B — guild and clan management tools | SOM $300M — gasless onboarding solutions for indie MMOGs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Guilds" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Seamlessly create and join guilds with gas-free onboarding and instant member transactions. Discipline: Game Design & Interactive Media (multiplayer coordination). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables gasless social wallet integration, vital for frictionless group formation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Guilds" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-loot-drops-1 Title: Sponsored Loot Drops Theme: Game Design & Interactive Media (games) · reward distribution Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute in-game rewards directly to players' wallets without any gas fees. Why Hedera: Hedera's fixed sub-cent fees allow frictionless token drops without user gas payments, improving user experience. Market: TAM $12B — global game rewards market | SAM $2.8B — indie game token economies | SOM $600M — gasless reward systems for digital collectibles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Loot Drops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute in-game rewards directly to players' wallets without any gas fees. Discipline: Game Design & Interactive Media (reward distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees allow frictionless token drops without user gas payments, improving user experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Loot Drops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-vr-lobby-2 Title: the embedded wallet VR Lobby Theme: Game Design & Interactive Media (games) · XR social spaces Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enter virtual lobbies with gas-free wallet login and seamless social interactions. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees ensures easy, gasless VR social access. Market: TAM $7B — XR social platform market | SAM $1.2B — immersive multiplayer spaces | SOM $250M — gasless onboarding in XR games ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet VR Lobby" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enter virtual lobbies with gas-free wallet login and seamless social interactions. Discipline: Game Design & Interactive Media (XR social spaces). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees ensures easy, gasless VR social access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet VR Lobby" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-onchain-avatar-store-3 Title: Onchain Avatar Store Theme: Game Design & Interactive Media (games) · digital fashion Hedera hook: Magic Link email wallet [wallet UX] Pitch: Buy and customize avatars with zero gas fees using embedded wallets and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees eliminates gas barriers in avatar item purchases. Market: TAM $5B — virtual fashion and avatar market | SAM $900M — indie avatar customization | SOM $200M — gasless avatar item transactions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Avatar Store" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Buy and customize avatars with zero gas fees using embedded wallets and Hedera's fixed sub-cent fees. Discipline: Game Design & Interactive Media (digital fashion). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees eliminates gas barriers in avatar item purchases. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Avatar Store" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-social-quest-chains-4 Title: Social Quest Chains Theme: Game Design & Interactive Media (games) · interactive storytelling Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collaborate on story-driven quests with gasless wallet sign-in and transaction sponsorship. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless collaboration with no gas hurdles. Market: TAM $4B — narrative-driven gaming market | SAM $750M — collaborative storytelling tools | SOM $150M — gasless transaction systems for quests ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Social Quest Chains" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaborate on story-driven quests with gasless wallet sign-in and transaction sponsorship. Discipline: Game Design & Interactive Media (interactive storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless collaboration with no gas hurdles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Social Quest Chains" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gasless-xr-art-swap-5 Title: Gasless XR Art Swap Theme: Game Design & Interactive Media (games) · interactive art exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Trade XR art pieces effortlessly with embedded wallets and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees removes gas friction in art trades. Market: TAM $3B — XR art marketplace | SAM $600M — indie interactive art platforms | SOM $120M — gasless NFT art swaps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless XR Art Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade XR art pieces effortlessly with embedded wallets and Hedera's fixed sub-cent fees. Discipline: Game Design & Interactive Media (interactive art exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees removes gas friction in art trades. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless XR Art Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-game-jam-6 Title: the embedded wallet Game Jam Theme: Game Design & Interactive Media (games) · developer community Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host game jams with onchain identity and gas-free participation transactions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows easy gasless user onboarding for events. Market: TAM $2B — game development event market | SAM $400M — indie game jam platforms | SOM $80M — gasless participant transactions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Game Jam" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host game jams with onchain identity and gas-free participation transactions. Discipline: Game Design & Interactive Media (developer community). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows easy gasless user onboarding for events. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Game Jam" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-beta-access-7 Title: Sponsored Beta Access Theme: Game Design & Interactive Media (games) · user testing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Grant beta testing access through gasless wallet authentication and sponsored invites. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable friction-free beta tester onboarding. Market: TAM $1.5B — game beta testing services | SAM $300M — indie testing platforms | SOM $60M — gasless invite systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Beta Access" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Grant beta testing access through gasless wallet authentication and sponsored invites. Discipline: Game Design & Interactive Media (user testing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable friction-free beta tester onboarding. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Beta Access" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-scoreboard-8 Title: the embedded wallet Scoreboard Theme: Game Design & Interactive Media (games) · competitive ranking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Display and update player rankings with gas-free wallet sign-in and transaction updates. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees keeps leaderboards updated without gas costs. Market: TAM $4.5B — esports and leaderboard market | SAM $850M — indie competitive platforms | SOM $170M — gasless leaderboard update tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Scoreboard" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Display and update player rankings with gas-free wallet sign-in and transaction updates. Discipline: Game Design & Interactive Media (competitive ranking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees keeps leaderboards updated without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Scoreboard" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-instant-nft-trades-9 Title: Instant NFT Trades Theme: Game Design & Interactive Media (games) · marketplace integration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Buy and sell game NFTs instantly with embedded wallets and zero gas fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows seamless, gasless NFT marketplace transactions. Market: TAM $20B — global NFT gaming market | SAM $4B — indie NFT marketplaces | SOM $800M — gasless NFT trading services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Instant NFT Trades" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Buy and sell game NFTs instantly with embedded wallets and zero gas fees. Discipline: Game Design & Interactive Media (marketplace integration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows seamless, gasless NFT marketplace transactions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Instant NFT Trades" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gasless-avatar-gifting-10 Title: Gasless Avatar Gifting Theme: Game Design & Interactive Media (games) · social gifting Hedera hook: Magic Link email wallet [wallet UX] Pitch: Send avatar skins and items as gifts without any gas fees. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees remove friction from gifting processes. Market: TAM $3.5B — virtual gifting market | SAM $700M — indie avatar economies | SOM $140M — gasless social gifting platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Avatar Gifting" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Send avatar skins and items as gifts without any gas fees. Discipline: Game Design & Interactive Media (social gifting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees remove friction from gifting processes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Avatar Gifting" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-skill-boosts-11 Title: Sponsored Skill Boosts Theme: Game Design & Interactive Media (games) · in-game perks Hedera hook: Magic Link email wallet [wallet UX] Pitch: Grant skill boosts and power-ups via gasless wallet transactions. Why Hedera: Embedded wallet plus Hedera's fixed sub-cent fees enables instant gas-free perk delivery. Market: TAM $6B — in-game purchase market | SAM $1.1B — indie game power-ups | SOM $220M — gasless boost delivery systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Skill Boosts" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Grant skill boosts and power-ups via gasless wallet transactions. Discipline: Game Design & Interactive Media (in-game perks). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Embedded wallet plus Hedera's fixed sub-cent fees enables instant gas-free perk delivery. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Skill Boosts" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-story-worlds-12 Title: the embedded wallet Story Worlds Theme: Game Design & Interactive Media (games) · user-generated content Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and share story-driven worlds with gasless wallet logins and sponsored content publishing. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees simplify content creation without gas costs. Market: TAM $4B — UGC game market | SAM $850M — indie narrative platforms | SOM $170M — gasless content publishing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Story Worlds" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and share story-driven worlds with gasless wallet logins and sponsored content publishing. Discipline: Game Design & Interactive Media (user-generated content). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees simplify content creation without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Story Worlds" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gas-free-co-op-play-13 Title: Gas-Free Co-op Play Theme: Game Design & Interactive Media (games) · multiplayer mechanics Hedera hook: Magic Link email wallet [wallet UX] Pitch: Join cooperative matches instantly with gasless wallet onboarding and sponsored sync transactions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees minimizes gas barriers for multiplayer joining. Market: TAM $10B — co-op game market | SAM $2B — indie multiplayer titles | SOM $400M — gasless multiplayer entry systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Co-op Play" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Join cooperative matches instantly with gasless wallet onboarding and sponsored sync transactions. Discipline: Game Design & Interactive Media (multiplayer mechanics). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees minimizes gas barriers for multiplayer joining. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Co-op Play" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-xr-avatars-14 Title: Sponsored XR Avatars Theme: Game Design & Interactive Media (games) · virtual identity Hedera hook: Magic Link email wallet [wallet UX] Pitch: Equip XR avatars with sponsored token transactions and easy wallet sign-in. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees streamline avatar customization gaslessly. Market: TAM $5B — XR avatar market | SAM $1B — indie XR developers | SOM $200M — gasless avatar upgrades ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored XR Avatars" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Equip XR avatars with sponsored token transactions and easy wallet sign-in. Discipline: Game Design & Interactive Media (virtual identity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees streamline avatar customization gaslessly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored XR Avatars" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-puzzle-rewards-15 Title: the embedded wallet Puzzle Rewards Theme: Game Design & Interactive Media (games) · casual game incentives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Earn puzzle completion rewards with gas-free wallet sign-in and transaction sponsorship. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless reward claim without gas hassles. Market: TAM $3B — casual gaming reward market | SAM $600M — indie puzzle games | SOM $120M — gasless reward claim platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Puzzle Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Earn puzzle completion rewards with gas-free wallet sign-in and transaction sponsorship. Discipline: Game Design & Interactive Media (casual game incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless reward claim without gas hassles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Puzzle Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gasless-avatar-battles-16 Title: Gasless Avatar Battles Theme: Game Design & Interactive Media (games) · competitive avatars Hedera hook: Magic Link email wallet [wallet UX] Pitch: Compete in avatar battles with gas-free wallet authentication and sponsored match transactions. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees remove friction from competitive gameplay actions. Market: TAM $7B — avatar battle games market | SAM $1.3B — indie competitive avatars | SOM $260M — gasless battle transaction systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Avatar Battles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Compete in avatar battles with gas-free wallet authentication and sponsored match transactions. Discipline: Game Design & Interactive Media (competitive avatars). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees remove friction from competitive gameplay actions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Avatar Battles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-xr-exhibits-17 Title: Sponsored XR Exhibits Theme: Game Design & Interactive Media (games) · virtual galleries Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host XR galleries that visitors explore with gasless wallet login and sponsored interaction tx. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplifies user access to XR galleries. Market: TAM $2.5B — virtual exhibit market | SAM $500M — indie XR galleries | SOM $100M — gasless visitor interaction tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored XR Exhibits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host XR galleries that visitors explore with gasless wallet login and sponsored interaction tx. Discipline: Game Design & Interactive Media (virtual galleries). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplifies user access to XR galleries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored XR Exhibits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-crafting-market-18 Title: the embedded wallet Crafting Market Theme: Game Design & Interactive Media (games) · player economies Hedera hook: Magic Link email wallet [wallet UX] Pitch: Trade crafted game items with gasless wallet sign-in and sponsored trades. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees improve player-to-player economy with no gas fees. Market: TAM $6B — in-game crafting markets | SAM $1.2B — indie player economies | SOM $240M — gasless item trading platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Crafting Market" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade crafted game items with gasless wallet sign-in and sponsored trades. Discipline: Game Design & Interactive Media (player economies). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees improve player-to-player economy with no gas fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Crafting Market" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gasless-streaming-rewards-19 Title: Gasless Streaming Rewards Theme: Game Design & Interactive Media (games) · viewer incentives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward game stream viewers with gas-free wallet transactions sponsored by creators. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable frictionless reward distribution to viewers. Market: TAM $8B — game streaming economy | SAM $1.6B — indie streamer rewards | SOM $320M — gasless viewer tipping platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Streaming Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward game stream viewers with gas-free wallet transactions sponsored by creators. Discipline: Game Design & Interactive Media (viewer incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable frictionless reward distribution to viewers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Streaming Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-fan-tokens-20 Title: Sponsored Fan Tokens Theme: Game Design & Interactive Media (games) · community engagement Hedera hook: Magic Link email wallet [wallet UX] Pitch: Issue fan engagement tokens to gamers using gasless wallet onboarding and sponsored mints. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees make fan token distribution smooth and gasless. Market: TAM $5B — fan token market | SAM $1B — indie community tokens | SOM $200M — gasless token issuance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Fan Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue fan engagement tokens to gamers using gasless wallet onboarding and sponsored mints. Discipline: Game Design & Interactive Media (community engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees make fan token distribution smooth and gasless. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Fan Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-challenge-leaderboards-21 Title: the embedded wallet Challenge Leaderboards Theme: Game Design & Interactive Media (games) · competitive tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track daily challenges and leaderboards with gasless wallet logins and sponsored updates. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees supports real-time leaderboard updates without user gas. Market: TAM $4B — challenge-based gaming market | SAM $800M — indie competitive platforms | SOM $160M — gasless leaderboard maintenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Challenge Leaderboards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track daily challenges and leaderboards with gasless wallet logins and sponsored updates. Discipline: Game Design & Interactive Media (competitive tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees supports real-time leaderboard updates without user gas. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Challenge Leaderboards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gas-free-level-sharing-22 Title: Gas-Free Level Sharing Theme: Game Design & Interactive Media (games) · user content Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share custom levels with friends through gasless wallet sign-in and sponsored access transactions. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable easy, gasless content sharing. Market: TAM $3B — user-generated level market | SAM $600M — indie game modding | SOM $120M — gasless content distribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Level Sharing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share custom levels with friends through gasless wallet sign-in and sponsored access transactions. Discipline: Game Design & Interactive Media (user content). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable easy, gasless content sharing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Level Sharing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sponsored-multiplayer-drops-23 Title: Sponsored Multiplayer Drops Theme: Game Design & Interactive Media (games) · event rewards Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute gasless event drops during multiplayer games with embedded wallets and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure smooth, gas-free event drop delivery. Market: TAM $7B — live multiplayer events market | SAM $1.3B — indie multiplayer events | SOM $260M — gasless event reward systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Multiplayer Drops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute gasless event drops during multiplayer games with embedded wallets and Hedera's fixed sub-cent fees. Discipline: Game Design & Interactive Media (event rewards). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure smooth, gas-free event drop delivery. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Multiplayer Drops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-privy-interactive-ads-24 Title: the embedded wallet Interactive Ads Theme: Game Design & Interactive Media (games) · ad integration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Engage users with interactive ads that deliver gasless rewards via embedded wallets. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows rewarding ad engagements without gas friction. Market: TAM $10B — in-game advertising market | SAM $2B — indie ad reward platforms | SOM $400M — gasless ad reward distribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Interactive Ads" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Engage users with interactive ads that deliver gasless rewards via embedded wallets. Discipline: Game Design & Interactive Media (ad integration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows rewarding ad engagements without gas friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Interactive Ads" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-provenance-playbooks-0 Title: Provenance Playbooks Theme: Game Design & Interactive Media (games) · game narrative design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create branching story games with verifiable original story assets minted onchain. Why Hedera: Ensures unique authorship and provenance of game narrative elements through NFT minting. Market: TAM $3B — narrative-driven game market | SAM $500M — indie narrative game developers | SOM $50M — studios adopting blockchain for storytelling ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Provenance Playbooks" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create branching story games with verifiable original story assets minted onchain. Discipline: Game Design & Interactive Media (game narrative design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Ensures unique authorship and provenance of game narrative elements through NFT minting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Provenance Playbooks" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-pixel-provenance-1 Title: Pixel Provenance Theme: Game Design & Interactive Media (games) · pixel art creation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint and verify original pixel art sprites as unique NFTs for games and assets. Why Hedera: NFT provenance certifies original pixel art ownership and distribution onchain. Market: TAM $2B — digital art assets in gaming | SAM $300M — indie pixel art creators | SOM $30M — blockchain-enabled art marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pixel Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and verify original pixel art sprites as unique NFTs for games and assets. Discipline: Game Design & Interactive Media (pixel art creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance certifies original pixel art ownership and distribution onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pixel Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-sound-slice-chain-2 Title: Sound Slice Chain Theme: Game Design & Interactive Media (games) · game audio sampling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and track original sound samples for interactive game soundtracks. Why Hedera: Provenance minting authenticates sound assets and prevents unauthorized reuse. Market: TAM $1.5B — game sound asset market | SAM $250M — independent sound designers | SOM $25M — blockchain-based audio licensing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound Slice Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and track original sound samples for interactive game soundtracks. Discipline: Game Design & Interactive Media (game audio sampling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting authenticates sound assets and prevents unauthorized reuse. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound Slice Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-vr-provenance-hub-3 Title: VR Provenance Hub Theme: Game Design & Interactive Media (games) · XR environment design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint immersive VR environments as NFTs to ensure creator authenticity and ownership. Why Hedera: Onchain provenance links environments uniquely to creator IPFS-hosted data. Market: TAM $12B — VR content creation | SAM $2B — XR indie developers | SOM $200M — blockchain-verified VR experiences ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VR Provenance Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint immersive VR environments as NFTs to ensure creator authenticity and ownership. Discipline: Game Design & Interactive Media (XR environment design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain provenance links environments uniquely to creator IPFS-hosted data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VR Provenance Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-avatar-dna-chain-4 Title: Avatar DNA Chain Theme: Game Design & Interactive Media (games) · custom avatar creation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create unique avatars with provenance-tracked traits minted as HTS NFT tokens. Why Hedera: NFT minting confirms original avatar trait ownership and prevents copying. Market: TAM $4B — avatar customization market | SAM $600M — indie avatar designers | SOM $60M — blockchain avatar platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Avatar DNA Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create unique avatars with provenance-tracked traits minted as HTS NFT tokens. Discipline: Game Design & Interactive Media (custom avatar creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting confirms original avatar trait ownership and prevents copying. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Avatar DNA Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-procedural-provenance-5 Title: Procedural Provenance Theme: Game Design & Interactive Media (games) · algorithmic game art Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint procedurally generated game art as NFTs linked to creator algorithms onchain. Why Hedera: Provenance minting ensures verifiable origin of algorithmically created assets. Market: TAM $1B — procedural art in games | SAM $150M — indie procedural artists | SOM $15M — blockchain procedural asset sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Procedural Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint procedurally generated game art as NFTs linked to creator algorithms onchain. Discipline: Game Design & Interactive Media (algorithmic game art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting ensures verifiable origin of algorithmically created assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Procedural Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-interactive-comics-chain-6 Title: Interactive Comics Chain Theme: Game Design & Interactive Media (games) · interactive storytelling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint interactive comic panels as unique NFTs with onchain provenance. Why Hedera: NFT minting uniquely ties interactive story elements to creators’ IPFS data. Market: TAM $2B — interactive comics market | SAM $350M — indie comic creators | SOM $35M — blockchain-backed story platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Comics Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint interactive comic panels as unique NFTs with onchain provenance. Discipline: Game Design & Interactive Media (interactive storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting uniquely ties interactive story elements to creators’ IPFS data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Comics Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-game-mod-provenance-7 Title: Game Mod Provenance Theme: Game Design & Interactive Media (games) · modding community tools Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique game mods with provenance to authenticate original creator contributions. Why Hedera: Onchain provenance prevents unauthorized mod copying and credits creators. Market: TAM $3B — game mod market | SAM $500M — indie modders | SOM $50M — blockchain mod distribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Game Mod Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique game mods with provenance to authenticate original creator contributions. Discipline: Game Design & Interactive Media (modding community tools). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain provenance prevents unauthorized mod copying and credits creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Game Mod Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-level-chain-creator-8 Title: Level Chain Creator Theme: Game Design & Interactive Media (games) · game level design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint custom game levels as NFTs with proof of originality and ownership onchain. Why Hedera: NFT provenance ensures unique ownership of user-created game levels. Market: TAM $2.5B — user-generated game levels | SAM $400M — indie level designers | SOM $40M — blockchain level marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Level Chain Creator" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint custom game levels as NFTs with proof of originality and ownership onchain. Discipline: Game Design & Interactive Media (game level design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures unique ownership of user-created game levels. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Level Chain Creator" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-replay-provenance-9 Title: Replay Provenance Theme: Game Design & Interactive Media (games) · game replay sharing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint verified game replays as NFTs to prove originality and preserve gameplay moments. Why Hedera: Provenance minting links replays securely to original gameplay data onchain. Market: TAM $1.2B — game streaming and replay | SAM $200M — replay content creators | SOM $20M — blockchain replay archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Replay Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint verified game replays as NFTs to prove originality and preserve gameplay moments. Discipline: Game Design & Interactive Media (game replay sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting links replays securely to original gameplay data onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Replay Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-xr-gesture-tokens-10 Title: XR Gesture Tokens Theme: Game Design & Interactive Media (games) · motion capture NFTs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original XR gesture captures as NFTs proving creator authorship. Why Hedera: Onchain minting certifies unique ownership of motion capture data. Market: TAM $800M — XR motion capture market | SAM $150M — indie motion artists | SOM $15M — blockchain gesture asset sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "XR Gesture Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original XR gesture captures as NFTs proving creator authorship. Discipline: Game Design & Interactive Media (motion capture NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain minting certifies unique ownership of motion capture data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "XR Gesture Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-lore-chain-artifacts-11 Title: Lore Chain Artifacts Theme: Game Design & Interactive Media (games) · game lore documentation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint in-game lore entries as NFTs to guarantee original creation and provenance. Why Hedera: NFT provenance confirms authenticity of lore content linked on IPFS. Market: TAM $1B — game lore markets | SAM $200M — indie lore creators | SOM $20M — blockchain lore collectibles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Lore Chain Artifacts" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint in-game lore entries as NFTs to guarantee original creation and provenance. Discipline: Game Design & Interactive Media (game lore documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance confirms authenticity of lore content linked on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Lore Chain Artifacts" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-ar-filter-provenance-12 Title: AR Filter Provenance Theme: Game Design & Interactive Media (games) · augmented reality effects Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create and mint AR filters as NFTs ensuring verified creator ownership. Why Hedera: Provenance minting secures original AR visual effect authorship onchain. Market: TAM $3B — AR filter market | SAM $400M — indie AR creators | SOM $40M — blockchain AR asset sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AR Filter Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and mint AR filters as NFTs ensuring verified creator ownership. Discipline: Game Design & Interactive Media (augmented reality effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting secures original AR visual effect authorship onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AR Filter Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-interactive-music-nfts-13 Title: Interactive Music NFTs Theme: Game Design & Interactive Media (games) · game soundtrack remixing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique interactive game soundtrack remixes with onchain provenance. Why Hedera: NFTs validate authentic creator rights over interactive music layers. Market: TAM $2B — game music remix market | SAM $300M — indie remix artists | SOM $30M — blockchain music licensing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Music NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique interactive game soundtrack remixes with onchain provenance. Discipline: Game Design & Interactive Media (game soundtrack remixing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs validate authentic creator rights over interactive music layers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Music NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-tokenized-game-guides-14 Title: Tokenized Game Guides Theme: Game Design & Interactive Media (games) · strategy content Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original game strategy guides as NFTs to prove and reward authorship. Why Hedera: Provenance minting secures guide originality and creator royalties onchain. Market: TAM $1.5B — game walkthrough market | SAM $250M — independent guide authors | SOM $25M — blockchain guide sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Game Guides" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original game strategy guides as NFTs to prove and reward authorship. Discipline: Game Design & Interactive Media (strategy content). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting secures guide originality and creator royalties onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Game Guides" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-dynamic-npc-tokens-15 Title: Dynamic NPC Tokens Theme: Game Design & Interactive Media (games) · procedural character design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint dynamic NPCs as NFTs with provable creator-generated traits and backstories. Why Hedera: NFT provenance links procedural NPC data securely to original creators. Market: TAM $2B — NPC asset market | SAM $350M — indie character designers | SOM $35M — blockchain NPC marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dynamic NPC Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint dynamic NPCs as NFTs with provable creator-generated traits and backstories. Discipline: Game Design & Interactive Media (procedural character design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance links procedural NPC data securely to original creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dynamic NPC Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-haptic-asset-chain-16 Title: Haptic Asset Chain Theme: Game Design & Interactive Media (games) · tactile feedback design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original haptic feedback patterns for XR as provenance-verified NFTs. Why Hedera: Onchain minting authenticates creator ownership of haptic interaction data. Market: TAM $700M — haptic feedback market | SAM $120M — indie haptic designers | SOM $12M — blockchain haptic asset sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Haptic Asset Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original haptic feedback patterns for XR as provenance-verified NFTs. Discipline: Game Design & Interactive Media (tactile feedback design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain minting authenticates creator ownership of haptic interaction data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Haptic Asset Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-ai-art-provenance-17 Title: AI Art Provenance Theme: Game Design & Interactive Media (games) · AI-generated game art Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint AI-generated game art NFTs with provenance proving creator guidance originality. Why Hedera: Provenance ensures proper attribution of AI-assisted art via NFT minting. Market: TAM $1.8B — AI art in gaming | SAM $300M — indie AI artists | SOM $30M — blockchain AI art sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AI Art Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint AI-generated game art NFTs with provenance proving creator guidance originality. Discipline: Game Design & Interactive Media (AI-generated game art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance ensures proper attribution of AI-assisted art via NFT minting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AI Art Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-multiplayer-skin-chain-18 Title: Multiplayer Skin Chain Theme: Game Design & Interactive Media (games) · cosmetic item creation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique multiplayer game skins as NFTs with verifiable creator provenance. Why Hedera: NFT provenance certifies cosmetic originality and ownership in multiplayer games. Market: TAM $4B — cosmetic skin market | SAM $700M — indie skin designers | SOM $70M — blockchain skin marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Multiplayer Skin Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique multiplayer game skins as NFTs with verifiable creator provenance. Discipline: Game Design & Interactive Media (cosmetic item creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance certifies cosmetic originality and ownership in multiplayer games. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Multiplayer Skin Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-puzzle-chain-creator-19 Title: Puzzle Chain Creator Theme: Game Design & Interactive Media (games) · interactive puzzle design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original interactive puzzles as NFTs proving unique creator ownership. Why Hedera: Provenance minting records puzzle originality and creator IPFS data onchain. Market: TAM $1B — puzzle game market | SAM $200M — indie puzzle designers | SOM $20M — blockchain puzzle asset sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Puzzle Chain Creator" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original interactive puzzles as NFTs proving unique creator ownership. Discipline: Game Design & Interactive Media (interactive puzzle design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting records puzzle originality and creator IPFS data onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Puzzle Chain Creator" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-narrative-voice-tokens-20 Title: Narrative Voice Tokens Theme: Game Design & Interactive Media (games) · voice acting assets Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original voiceover clips for games as provenance-verified NFTs. Why Hedera: NFT provenance confirms voice asset originality and creator rights. Market: TAM $1.3B — voice asset market | SAM $220M — indie voice actors | SOM $22M — blockchain voice clip sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Voice Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original voiceover clips for games as provenance-verified NFTs. Discipline: Game Design & Interactive Media (voice acting assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance confirms voice asset originality and creator rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Voice Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-interactive-map-nfts-21 Title: Interactive Map NFTs Theme: Game Design & Interactive Media (games) · game world mapping Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original interactive game maps as NFTs with verified creator provenance. Why Hedera: NFT minting secures unique ownership of created game maps onchain. Market: TAM $1.7B — game map asset market | SAM $280M — indie map creators | SOM $28M — blockchain map marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Map NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original interactive game maps as NFTs with verified creator provenance. Discipline: Game Design & Interactive Media (game world mapping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting secures unique ownership of created game maps onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Map NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-collectible-lore-cards-22 Title: Collectible Lore Cards Theme: Game Design & Interactive Media (games) · digital collectible design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique lore cards as NFTs proving authentic creator provenance. Why Hedera: Provenance minting ensures collectible authenticity and creator ownership onchain. Market: TAM $2B — collectible card market | SAM $400M — indie collectible designers | SOM $40M — blockchain card marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collectible Lore Cards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique lore cards as NFTs proving authentic creator provenance. Discipline: Game Design & Interactive Media (digital collectible design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting ensures collectible authenticity and creator ownership onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collectible Lore Cards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-gesture-control-assets-23 Title: Gesture-Control Assets Theme: Game Design & Interactive Media (games) · XR gesture interaction Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique XR gesture control assets as NFTs ensuring original creator rights. Why Hedera: NFT provenance binds gesture assets securely to creators’ IPFS content. Market: TAM $900M — XR interaction assets | SAM $150M — indie gesture creators | SOM $15M — blockchain gesture sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gesture-Control Assets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique XR gesture control assets as NFTs ensuring original creator rights. Discipline: Game Design & Interactive Media (XR gesture interaction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance binds gesture assets securely to creators’ IPFS content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gesture-Control Assets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA games-game-font-provenance-24 Title: Game Font Provenance Theme: Game Design & Interactive Media (games) · typography for games Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique game fonts as NFTs proving original designer ownership. Why Hedera: Provenance minting authenticates font designs linked on IPFS to creators. Market: TAM $600M — game font market | SAM $100M — indie typographers | SOM $10M — blockchain font sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Game Font Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique game fonts as NFTs proving original designer ownership. Discipline: Game Design & Interactive Media (typography for games). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Provenance minting authenticates font designs linked on IPFS to creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Game Font Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Music & Sound Design musicians, producers, composers, sound designers ============================================================================== ------------------------------------------------------------------------------ IDEA music-loop-provenance-0 Title: Loop Provenance Theme: Music & Sound Design (music) · sample tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and verify original creators of music loops and samples to credit and reward authors accurately. Why Hedera: Hedera testnet smart contracts provide immutable provenance records for sound samples, preventing forgery. Market: TAM $2B — global market for sample libraries | SAM $500M — licensed sample usage among producers | SOM $50M — early adopters in legal sample attribution tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Loop Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and verify origin of audio loops to ensure authenticity for musicians and producers. Discipline: Music & Sound Design (sample lineage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides immutable storage of loop metadata proving origin and version history. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Loop Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-remix-rights-1 Title: Onchain Remix Rights Theme: Music & Sound Design (music) · remix licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable transparent licensing and royalty splits for remixed tracks directly on the blockchain. Why Hedera: Smart contracts automate rights management and payments without intermediaries. Market: TAM $3B — remix music production market | SAM $700M — digital remix licensing sector | SOM $70M — independent remixer user base ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Remix Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable transparent licensing and royalty splits for remixed tracks directly on the blockchain. Discipline: Music & Sound Design (remix licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate rights management and payments without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Remix Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-collaborative-beat-ledger-2 Title: Collaborative Beat Ledger Theme: Music & Sound Design (music) · beat collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record and timestamp collaborative beat creation sessions with immutable proof of contribution. Why Hedera: Hedera testnet contracts ensure transparent, tamper-proof proof of co-production. Market: TAM $1.5B — collaborative music platforms | SAM $400M — beatmaking communities online | SOM $40M — active beat collaboration users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Beat Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and timestamp collaborative beat creation sessions with immutable proof of contribution. Discipline: Music & Sound Design (beat collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts ensure transparent, tamper-proof proof of co-production. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Beat Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sound-effect-nft-vault-3 Title: Sound Effect NFT Vault Theme: Music & Sound Design (music) · sound libraries Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint and trade exclusive sound effects as NFTs ensuring ownership and monetization for creators. Why Hedera: Smart contracts enable unique, verifiable ownership of sound assets. Market: TAM $800M — sound effect market for media | SAM $200M — digital sound asset sales | SOM $25M — niche sound NFT collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound Effect NFT Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade exclusive sound effects as NFTs ensuring ownership and monetization for creators. Discipline: Music & Sound Design (sound libraries). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable unique, verifiable ownership of sound assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound Effect NFT Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-sample-swap-4 Title: Onchain Sample Swap Theme: Music & Sound Design (music) · sample exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Facilitate peer-to-peer swapping of music samples with provable ownership and usage rights. Why Hedera: Hedera testnet contracts provide trustless verification in the exchange process. Market: TAM $1.2B — sample sharing market | SAM $300M — licensed sample swap communities | SOM $35M — active swap platform users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Sample Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate peer-to-peer swapping of music samples with provable ownership and usage rights. Discipline: Music & Sound Design (sample exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide trustless verification in the exchange process. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Sample Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-live-set-royalty-split-5 Title: Live Set Royalty Split Theme: Music & Sound Design (music) · live performance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automatically distribute royalty shares from live streamed performances via smart contracts. Why Hedera: Onchain automation reduces complexity in live earnings distribution. Market: TAM $4B — live music streaming economy | SAM $1B — royalty management for live artists | SOM $100M — early live performer adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Live Set Royalty Split" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically distribute royalty shares from live streamed performances via smart contracts. Discipline: Music & Sound Design (live performance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain automation reduces complexity in live earnings distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Live Set Royalty Split" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-ai-audio-license-6 Title: AI Audio License Theme: Music & Sound Design (music) · AI-generated music Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Onchain licenses for AI-created audio to prevent misuse and ensure creator credit. Why Hedera: Smart contracts track AI output provenance and rights enforcement. Market: TAM $600M — AI music generation | SAM $150M — AI music licensing | SOM $20M — AI music creators adopting blockchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AI Audio License" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Onchain licenses for AI-created audio to prevent misuse and ensure creator credit. Discipline: Music & Sound Design (AI-generated music). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts track AI output provenance and rights enforcement. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AI Audio License" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-music-curriculum-7 Title: Onchain Music Curriculum Theme: Music & Sound Design (music) · music education Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Certify students’ composition and sound design achievements onchain for transparent credentialing. Why Hedera: Immutable record of skill acquisition ensures authenticity in credentials. Market: TAM $3B — global music education | SAM $800M — online music learning platforms | SOM $80M — blockchain-verified certificate users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Music Curriculum" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Certify students’ composition and sound design achievements onchain for transparent credentialing. Discipline: Music & Sound Design (music education). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable record of skill acquisition ensures authenticity in credentials. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Music Curriculum" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-tokenized-composer-credits-8 Title: Tokenized Composer Credits Theme: Music & Sound Design (music) · credit attribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue tokens representing composer credits to ensure fair recognition and revenue sharing. Why Hedera: Hedera testnet smart contracts make composer credit tamper-proof and tradable. Market: TAM $5B — global composition markets | SAM $1.2B — digital composer rights management | SOM $120M — composer token adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Composer Credits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue tokens representing composer credits to ensure fair recognition and revenue sharing. Discipline: Music & Sound Design (credit attribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts make composer credit tamper-proof and tradable. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Composer Credits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sound-asset-crowdfunding-9 Title: Sound Asset Crowdfunding Theme: Music & Sound Design (music) · music funding Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Raise decentralized funding for music and sound projects with onchain milestone payments. Why Hedera: Smart contracts automate fund release upon achievement of creative goals. Market: TAM $700M — music crowdfunding market | SAM $180M — digital creative project funding | SOM $22M — music crowdfunding blockchain users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound Asset Crowdfunding" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Raise decentralized funding for music and sound projects with onchain milestone payments. Discipline: Music & Sound Design (music funding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate fund release upon achievement of creative goals. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound Asset Crowdfunding" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-synth-presets-10 Title: Onchain Synth Presets Theme: Music & Sound Design (music) · synth programming Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Distribute unique synthesizer presets as verifiable NFTs with built-in royalty logic. Why Hedera: Smart contracts guarantee authenticity and automate resale royalties. Market: TAM $900M — synth software market | SAM $250M — preset pack sales | SOM $30M — synth preset NFT buyers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Synth Presets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute unique synthesizer presets as verifiable NFTs with built-in royalty logic. Discipline: Music & Sound Design (synth programming). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts guarantee authenticity and automate resale royalties. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Synth Presets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-decentralized-jam-sessions-11 Title: Decentralized Jam Sessions Theme: Music & Sound Design (music) · remote collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record and timestamp decentralized jam sessions to prove creative input for all participants. Why Hedera: Hedera testnet contracts provide transparent, shared ownership and contribution logs. Market: TAM $1.8B — remote music collaboration | SAM $450M — collaborative streaming platforms | SOM $50M — blockchain jam session participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Jam Sessions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and timestamp decentralized jam sessions to prove creative input for all participants. Discipline: Music & Sound Design (remote collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide transparent, shared ownership and contribution logs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Jam Sessions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-interactive-sound-nfts-12 Title: Interactive Sound NFTs Theme: Music & Sound Design (music) · dynamic audio Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create NFTs with interactive sound design that evolves based on user input and onchain data. Why Hedera: Smart contracts enable programmable audio behavior tied to token ownership. Market: TAM $1.1B — interactive media market | SAM $300M — audio NFT sales | SOM $35M — interactive sound collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Sound NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs with interactive sound design that evolves based on user input and onchain data. Discipline: Music & Sound Design (dynamic audio). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable programmable audio behavior tied to token ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Sound NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-remix-competitions-13 Title: Onchain Remix Competitions Theme: Music & Sound Design (music) · contest management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Host remix contests with transparent voting and automatic prize payouts on the blockchain. Why Hedera: Smart contracts enforce fairness and instant distribution of rewards. Market: TAM $2B — music competition market | SAM $500M — online remix contests | SOM $60M — blockchain contest platform users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Remix Competitions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host remix contests with transparent voting and automatic prize payouts on the blockchain. Discipline: Music & Sound Design (contest management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enforce fairness and instant distribution of rewards. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Remix Competitions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sample-chain-marketplace-14 Title: Sample Chain Marketplace Theme: Music & Sound Design (music) · music marketplaces Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create a decentralized marketplace for verified, licensed music samples with automatic royalties. Why Hedera: Hedera testnet contracts reduce intermediaries and enforce payment distribution. Market: TAM $1.7B — digital sample sales | SAM $400M — licensed sample marketplaces | SOM $45M — blockchain sample marketplace users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sample Chain Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a decentralized marketplace for verified, licensed music samples with automatic royalties. Discipline: Music & Sound Design (music marketplaces). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts reduce intermediaries and enforce payment distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sample Chain Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-audio-stems-15 Title: Onchain Audio Stems Theme: Music & Sound Design (music) · stem distribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Distribute song stems with immutable licenses and resale royalty tracking onchain. Why Hedera: Smart contracts ensure transparent stem ownership and usage rights. Market: TAM $2.5B — stem-based music production | SAM $600M — digital stem trading | SOM $70M — onchain stem marketplace adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Audio Stems" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute song stems with immutable licenses and resale royalty tracking onchain. Discipline: Music & Sound Design (stem distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts ensure transparent stem ownership and usage rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Audio Stems" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-blockchain-soundscapes-16 Title: Blockchain Soundscapes Theme: Music & Sound Design (music) · environmental audio Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Share and license unique recorded soundscapes with verified origin and ownership on blockchain. Why Hedera: Immutable records authenticate soundscape provenance and creator rights. Market: TAM $500M — environmental sound libraries | SAM $120M — soundscape licensing | SOM $15M — blockchain soundscape users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Blockchain Soundscapes" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share and license unique recorded soundscapes with verified origin and ownership on blockchain. Discipline: Music & Sound Design (environmental audio). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable records authenticate soundscape provenance and creator rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Blockchain Soundscapes" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-decentralized-music-feedback-17 Title: Decentralized Music Feedback Theme: Music & Sound Design (music) · peer review Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Collect transparent, timestamped peer reviews for music tracks to build trust and credibility. Why Hedera: Onchain feedback prevents censorship and alteration of critiques. Market: TAM $1.3B — peer review music platforms | SAM $350M — online music feedback | SOM $40M — blockchain feedback users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Music Feedback" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collect transparent, timestamped peer reviews for music tracks to build trust and credibility. Discipline: Music & Sound Design (peer review). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain feedback prevents censorship and alteration of critiques. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Music Feedback" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-dj-set-logs-18 Title: Onchain DJ Set Logs Theme: Music & Sound Design (music) · performance archiving Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create immutable records of DJ set tracklists and transitions for copyright and reputation. Why Hedera: Hedera testnet contracts provide verifiable event histories for DJs. Market: TAM $1.6B — DJ software market | SAM $450M — live set archiving services | SOM $50M — blockchain DJ set recorders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain DJ Set Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create immutable records of DJ set tracklists and transitions for copyright and reputation. Discipline: Music & Sound Design (performance archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide verifiable event histories for DJs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain DJ Set Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-smart-contract-audio-rng-19 Title: Smart Contract Audio RNG Theme: Music & Sound Design (music) · generative music Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Use onchain randomness to generate unique, verifiable generative music pieces as NFTs. Why Hedera: Hedera testnet’s secure RNG enables trustable generative outputs onchain. Market: TAM $800M — generative music market | SAM $220M — NFT generative compositions | SOM $25M — blockchain generative music buyers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Smart Contract Audio RNG" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use onchain randomness to generate unique, verifiable generative music pieces as NFTs. Discipline: Music & Sound Design (generative music). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet’s secure RNG enables trustable generative outputs onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Smart Contract Audio RNG" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-token-gated-music-releases-20 Title: Token-Gated Music Releases Theme: Music & Sound Design (music) · exclusive content Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Release exclusive music content accessible only to token holders via smart contracts. Why Hedera: Smart contracts manage access rights and track exclusivity automatically. Market: TAM $3.5B — premium music content | SAM $900M — token-gated release platforms | SOM $90M — blockchain music exclusivity users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Token-Gated Music Releases" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Release exclusive music content accessible only to token holders via smart contracts. Discipline: Music & Sound Design (exclusive content). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts manage access rights and track exclusivity automatically. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Token-Gated Music Releases" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-vocal-processing-21 Title: Onchain Vocal Processing Theme: Music & Sound Design (music) · vocal effects Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Store and verify customizable vocal effect presets and chains onchain for sharing and licensing. Why Hedera: Immutable contracts authenticate effect creations and usage rights. Market: TAM $1B — vocal effects market | SAM $280M — effect preset sales | SOM $30M — blockchain vocal effect users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Vocal Processing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and verify customizable vocal effect presets and chains onchain for sharing and licensing. Discipline: Music & Sound Design (vocal effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable contracts authenticate effect creations and usage rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Vocal Processing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-music-metadata-registry-22 Title: Music Metadata Registry Theme: Music & Sound Design (music) · metadata management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create a decentralized registry for verified, searchable music metadata and ownership info. Why Hedera: Smart contracts ensure metadata integrity and decentralized availability. Market: TAM $2.8B — music information management | SAM $700M — digital metadata services | SOM $80M — blockchain metadata users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Music Metadata Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a decentralized registry for verified, searchable music metadata and ownership info. Discipline: Music & Sound Design (metadata management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts ensure metadata integrity and decentralized availability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Music Metadata Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-smart-contract-sync-licenses-23 Title: Smart Contract Sync Licenses Theme: Music & Sound Design (music) · sync licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate sync rights clearance and royalty splits for film, TV, and ads on the blockchain. Why Hedera: Onchain contracts reduce delays and disputes in sync licensing. Market: TAM $4.5B — sync licensing market | SAM $1.2B — digital sync rights management | SOM $125M — blockchain sync license adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Smart Contract Sync Licenses" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate sync rights clearance and royalty splits for film, TV, and ads on the blockchain. Discipline: Music & Sound Design (sync licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain contracts reduce delays and disputes in sync licensing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Smart Contract Sync Licenses" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-audio-watermarking-24 Title: Onchain Audio Watermarking Theme: Music & Sound Design (music) · copyright protection Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Embed immutable proof of ownership and usage terms in audio files verified via blockchain. Why Hedera: Hedera testnet contracts provide tamper-proof evidence of audio copyright. Market: TAM $1.3B — audio copyright management | SAM $350M — digital watermarking technologies | SOM $40M — blockchain watermark adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Audio Watermarking" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Embed immutable proof of ownership and usage terms in audio files verified via blockchain. Discipline: Music & Sound Design (copyright protection). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide tamper-proof evidence of audio copyright. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Audio Watermarking" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-loop-provenance-0 Title: Loop Provenance Theme: Music & Sound Design (music) · sample lineage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track and verify origin of audio loops to ensure authenticity for musicians and producers. Why Hedera: IPFS provides immutable storage of loop metadata proving origin and version history. Market: TAM $2B — global audio loop market | SAM $500M — sample pack and loop sales | SOM $50M — producers using authenticated loop libraries ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Loop Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and verify origin of audio loops to ensure authenticity for musicians and producers. Discipline: Music & Sound Design (sample lineage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides immutable storage of loop metadata proving origin and version history. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Loop Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-patch-vault-1 Title: Patch Vault Theme: Music & Sound Design (music) · synth preset archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely store and share synthesizer presets with verified authenticity and version control. Why Hedera: Pinata IPFS immutably hosts preset files and metadata accessible globally. Market: TAM $1.5B — global synth and preset market | SAM $300M — digital synth presets sales | SOM $25M — sound designers sharing presets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Patch Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and share synthesizer presets with verified authenticity and version control. Discipline: Music & Sound Design (synth preset archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS immutably hosts preset files and metadata accessible globally. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Patch Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-score-archive-2 Title: Score Archive Theme: Music & Sound Design (music) · composition notation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently archive music scores and compositions with guaranteed timestamped records. Why Hedera: IPFS storage ensures unalterable score files accessible forever. Market: TAM $1B — music publishing and notation software | SAM $200M — digital score resale and licensing | SOM $15M — composers storing verified originals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Score Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently archive music scores and compositions with guaranteed timestamped records. Discipline: Music & Sound Design (composition notation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS storage ensures unalterable score files accessible forever. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Score Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-samplechain-3 Title: SampleChain Theme: Music & Sound Design (music) · sample licensing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Manage and prove sample license ownership with permanent onchain records for producers. Why Hedera: Pinata IPFS stores license documents and metadata linked to sample CIDs. Market: TAM $3B — sample licensing industry | SAM $700M — sample license transactions digitally | SOM $60M — independent producers licensing samples ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SampleChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and prove sample license ownership with permanent onchain records for producers. Discipline: Music & Sound Design (sample licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS stores license documents and metadata linked to sample CIDs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SampleChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-mix-history-4 Title: Mix History Theme: Music & Sound Design (music) · mix versioning Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track iterative versions of audio mixes ensuring creators can revert and prove mix lineage. Why Hedera: IPFS pins mix files with immutable version metadata for transparent history. Market: TAM $1B — audio mixing software market | SAM $250M — online mix collaboration tools | SOM $20M — studios with version-tracked mixes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Mix History" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track iterative versions of audio mixes ensuring creators can revert and prove mix lineage. Discipline: Music & Sound Design (mix versioning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins mix files with immutable version metadata for transparent history. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Mix History" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-soundscape-atlas-5 Title: Soundscape Atlas Theme: Music & Sound Design (music) · field recording Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Catalog and permanently archive environmental soundscapes for reuse and licensing. Why Hedera: IPFS stores large audio files and metadata ensuring long-term access and provenance. Market: TAM $300M — field recording industry | SAM $80M — licensed environmental sound libraries | SOM $10M — sound designers curating soundscapes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Soundscape Atlas" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Catalog and permanently archive environmental soundscapes for reuse and licensing. Discipline: Music & Sound Design (field recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores large audio files and metadata ensuring long-term access and provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Soundscape Atlas" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-preset-gallery-6 Title: Preset Gallery Theme: Music & Sound Design (music) · plugin preset sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create a decentralized gallery of verified plugin presets with community ratings. Why Hedera: Pinata IPFS hosts presets guaranteeing tamper-proof sharing and retrieval. Market: TAM $1.2B — music plugin market | SAM $350M — plugin preset marketplace | SOM $30M — producers accessing verified presets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Preset Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a decentralized gallery of verified plugin presets with community ratings. Discipline: Music & Sound Design (plugin preset sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS hosts presets guaranteeing tamper-proof sharing and retrieval. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Preset Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-vinyl-metadata-7 Title: Vinyl Metadata Theme: Music & Sound Design (music) · record collection Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Digitally archive vinyl metadata linked to physical records for collectors and sellers. Why Hedera: IPFS stores immutable record metadata accessible worldwide and permanently. Market: TAM $500M — vinyl collector market | SAM $120M — online vinyl metadata databases | SOM $12M — sellers verifying vinyl provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Vinyl Metadata" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitally archive vinyl metadata linked to physical records for collectors and sellers. Discipline: Music & Sound Design (record collection). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores immutable record metadata accessible worldwide and permanently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Vinyl Metadata" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-moodsampler-8 Title: MoodSampler Theme: Music & Sound Design (music) · emotional tagging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Attach permanent mood and emotion tags to samples for easy discovery and curation. Why Hedera: IPFS stores mood metadata linked with sample files ensuring permanent tagging. Market: TAM $600M — sample discovery platforms | SAM $150M — tagged sample sales | SOM $18M — producers using mood-tagged loops ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoodSampler" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach permanent mood and emotion tags to samples for easy discovery and curation. Discipline: Music & Sound Design (emotional tagging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores mood metadata linked with sample files ensuring permanent tagging. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoodSampler" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-beatledger-9 Title: BeatLedger Theme: Music & Sound Design (music) · beat ownership Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Record and prove ownership of beats and instrumentals on a decentralized archive. Why Hedera: IPFS ensures beat files and ownership proofs are immutable and always-accessible. Market: TAM $900M — beat marketplace | SAM $220M — independent beat sales | SOM $25M — producers establishing beat provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "BeatLedger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and prove ownership of beats and instrumentals on a decentralized archive. Discipline: Music & Sound Design (beat ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures beat files and ownership proofs are immutable and always-accessible. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "BeatLedger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-synth-patchbook-10 Title: Synth Patchbook Theme: Music & Sound Design (music) · sound design catalog Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create a permanent catalog of unique synth patches for community sharing and licensing. Why Hedera: Pinata IPFS hosts patch files and metadata ensuring authenticity and longevity. Market: TAM $1.1B — synth sound design tools | SAM $280M — shared patch marketplaces | SOM $22M — sound designers trading patches ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Synth Patchbook" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a permanent catalog of unique synth patches for community sharing and licensing. Discipline: Music & Sound Design (sound design catalog). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS hosts patch files and metadata ensuring authenticity and longevity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Synth Patchbook" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-remix-rights-11 Title: Remix Rights Theme: Music & Sound Design (music) · remix management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track remix permissions and derivatives with permanent records linked to original works. Why Hedera: IPFS stores remix manifests ensuring immutable rights documentation. Market: TAM $1.3B — remix licensing market | SAM $320M — remix rights management tools | SOM $30M — artists managing remix documentation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Remix Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track remix permissions and derivatives with permanent records linked to original works. Discipline: Music & Sound Design (remix management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores remix manifests ensuring immutable rights documentation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Remix Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-loopswap-12 Title: LoopSwap Theme: Music & Sound Design (music) · loop exchange Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Decentralized platform for permanent loop exchange with verified content provenance. Why Hedera: IPFS pins loops and transaction manifests guaranteeing content immutability. Market: TAM $2.1B — loop and sample exchange market | SAM $600M — digital loop sales | SOM $45M — producers swapping verified loops ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LoopSwap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralized platform for permanent loop exchange with verified content provenance. Discipline: Music & Sound Design (loop exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins loops and transaction manifests guaranteeing content immutability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LoopSwap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-patch-ledger-13 Title: Patch Ledger Theme: Music & Sound Design (music) · sound patch tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Record creation and usage history of sound patches to prove originality and licensing. Why Hedera: IPFS stores patches and metadata ensuring unchangeable usage logs. Market: TAM $1B — plugin preset market | SAM $250M — sound patch licensing | SOM $20M — designers protecting patch IP ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Patch Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record creation and usage history of sound patches to prove originality and licensing. Discipline: Music & Sound Design (sound patch tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores patches and metadata ensuring unchangeable usage logs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Patch Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-composer-collab-14 Title: Composer Collab Theme: Music & Sound Design (music) · composition sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely share and timestamp collaborative compositions with permanent version control. Why Hedera: IPFS immutably pins composition files and revision manifests for transparency. Market: TAM $900M — online composition tools | SAM $230M — collaborative composition market | SOM $18M — composers using verified shared scores ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Composer Collab" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely share and timestamp collaborative compositions with permanent version control. Discipline: Music & Sound Design (composition sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS immutably pins composition files and revision manifests for transparency. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Composer Collab" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sound-nft-15 Title: Sound NFT Theme: Music & Sound Design (music) · audio collectibles Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create permanent and verifiable audio NFTs with linked metadata on decentralized storage. Why Hedera: Pinata IPFS ensures NFT audio assets remain immutable and accessible. Market: TAM $3B — music NFT market | SAM $800M — audio NFT sales | SOM $70M — musicians minting audio NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create permanent and verifiable audio NFTs with linked metadata on decentralized storage. Discipline: Music & Sound Design (audio collectibles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS ensures NFT audio assets remain immutable and accessible. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-samplechain-pro-16 Title: SampleChain Pro Theme: Music & Sound Design (music) · sample provenance Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Professional tool to trace sample origins and licensing history with permanent records. Why Hedera: IPFS hosts sample metadata and provenance documents immutably and transparently. Market: TAM $3.5B — professional sample market | SAM $900M — sample provenance tools | SOM $80M — studios verifying sample history ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SampleChain Pro" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Professional tool to trace sample origins and licensing history with permanent records. Discipline: Music & Sound Design (sample provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS hosts sample metadata and provenance documents immutably and transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SampleChain Pro" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-liveloop-archive-17 Title: LiveLoop Archive Theme: Music & Sound Design (music) · live set recording Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently store live loop sets with detailed metadata for reuse and proof of creation. Why Hedera: IPFS pins large live loop files ensuring permanent access and origin verification. Market: TAM $1.4B — live performance software | SAM $350M — live set archival tools | SOM $28M — live musicians archiving sessions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LiveLoop Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently store live loop sets with detailed metadata for reuse and proof of creation. Discipline: Music & Sound Design (live set recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins large live loop files ensuring permanent access and origin verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LiveLoop Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-soundpatch-market-18 Title: SoundPatch Market Theme: Music & Sound Design (music) · preset marketplace Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Decentralized marketplace for buying and selling sound patches with permanent proof of originality. Why Hedera: Pinata IPFS stores patch files and transaction history immutably. Market: TAM $1.3B — digital preset market | SAM $400M — preset sales platforms | SOM $30M — buyers verifying patch legitimacy ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SoundPatch Market" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralized marketplace for buying and selling sound patches with permanent proof of originality. Discipline: Music & Sound Design (preset marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS stores patch files and transaction history immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SoundPatch Market" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-genre-mapper-19 Title: Genre Mapper Theme: Music & Sound Design (music) · music classification Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Attach permanent genre and style metadata to tracks for better discovery and curation. Why Hedera: IPFS stores genre tags linked to audio files permanently and transparently. Market: TAM $800M — music metadata services | SAM $180M — genre tagging platforms | SOM $15M — producers using classified samples ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Genre Mapper" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach permanent genre and style metadata to tracks for better discovery and curation. Discipline: Music & Sound Design (music classification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS stores genre tags linked to audio files permanently and transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Genre Mapper" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-audio-blueprint-20 Title: Audio Blueprint Theme: Music & Sound Design (music) · sound design templates Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Archive reusable sound design templates with verified authorship and version tracking. Why Hedera: IPFS hosts template files ensuring permanent access and identity proof. Market: TAM $1B — sound design template market | SAM $250M — template sales platforms | SOM $20M — designers sharing verified templates ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Audio Blueprint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive reusable sound design templates with verified authorship and version tracking. Discipline: Music & Sound Design (sound design templates). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS hosts template files ensuring permanent access and identity proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Audio Blueprint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-loopchain-sync-21 Title: LoopChain Sync Theme: Music & Sound Design (music) · collaborative looping Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Synchronize loop edits in real-time with permanent version storage on decentralized archive. Why Hedera: IPFS pins loop versions ensuring immutable history and shared access. Market: TAM $1.6B — collaborative music software | SAM $400M — online loop collaboration | SOM $30M — musicians using synced loops ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LoopChain Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Synchronize loop edits in real-time with permanent version storage on decentralized archive. Discipline: Music & Sound Design (collaborative looping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pins loop versions ensuring immutable history and shared access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LoopChain Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-dynamic-scores-22 Title: Dynamic Scores Theme: Music & Sound Design (music) · interactive notation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store interactive music scores with embedded sounds permanently for composers and educators. Why Hedera: IPFS hosts interactive score files ensuring persistent availability and integrity. Market: TAM $700M — digital score software | SAM $150M — interactive score solutions | SOM $12M — educators using verified interactive scores ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dynamic Scores" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store interactive music scores with embedded sounds permanently for composers and educators. Discipline: Music & Sound Design (interactive notation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS hosts interactive score files ensuring persistent availability and integrity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dynamic Scores" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-stem-provenance-23 Title: Stem Provenance Theme: Music & Sound Design (music) · multitrack tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track origin and licensing of individual stems with permanent onchain metadata storage. Why Hedera: Pinata IPFS immutably stores stem files and associated licensing info. Market: TAM $1.8B — multitrack audio market | SAM $400M — stem licensing platforms | SOM $35M — producers verifying stem usage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Stem Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track origin and licensing of individual stems with permanent onchain metadata storage. Discipline: Music & Sound Design (multitrack tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS immutably stores stem files and associated licensing info. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Stem Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-remix-exchange-0 Title: Gasless Remix Exchange Theme: Music & Sound Design (music) · collaborative remixing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collaborate seamlessly on remixes without blockchain fees or sign-up friction. Why Hedera: Hedera's fixed sub-cent fees let users remix and share without paying gas or managing wallets. Market: TAM $800M — global remix platform software | SAM $250M — remix collaboration tools | SOM $12M — gasless remixing apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Remix Exchange" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaborate seamlessly on remixes without blockchain fees or sign-up friction. Discipline: Music & Sound Design (collaborative remixing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees let users remix and share without paying gas or managing wallets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Remix Exchange" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-sound-packs-1 Title: Onchain Sound Packs Theme: Music & Sound Design (music) · sample library distribution Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute sound packs with verified authenticity and instant user access via gasless wallets. Why Hedera: Magic Link email sign-ins enable frictionless user onboarding and free samples delivery onchain. Market: TAM $1B — digital sample market | SAM $300M — sample subscription services | SOM $15M — verified sample delivery ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Sound Packs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute sound packs with verified authenticity and instant user access via gasless wallets. Discipline: Music & Sound Design (sample library distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins enable frictionless user onboarding and free samples delivery onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Sound Packs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-nft-beat-marketplace-2 Title: NFT Beat Marketplace Theme: Music & Sound Design (music) · beat selling Hedera hook: Magic Link email wallet [wallet UX] Pitch: Buy and sell beats with onchain provenance and zero gas fee transactions. Why Hedera: Hedera's fixed sub-cent fees allow beat creators to sell NFTs without buyers worrying about gas costs. Market: TAM $600M — digital beat marketplace | SAM $120M — NFT music sales | SOM $10M — gasless NFT beat sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Beat Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Buy and sell beats with onchain provenance and zero gas fee transactions. Discipline: Music & Sound Design (beat selling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees allow beat creators to sell NFTs without buyers worrying about gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Beat Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-instant-royalty-splits-3 Title: Instant Royalty Splits Theme: Music & Sound Design (music) · music rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Automatically split royalties among collaborators with transparent, gasless onchain transactions. Why Hedera: Magic Link email sign-ins enable seamless royalty payments without fees, encouraging fair splits. Market: TAM $7B — global royalty management | SAM $1.5B — digital royalty platforms | SOM $80M — automated royalty splitting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Instant Royalty Splits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically split royalties among collaborators with transparent, gasless onchain transactions. Discipline: Music & Sound Design (music rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins enable seamless royalty payments without fees, encouraging fair splits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Instant Royalty Splits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gas-free-live-sampling-4 Title: Gas-Free Live Sampling Theme: Music & Sound Design (music) · live-set sampling Hedera hook: Magic Link email wallet [wallet UX] Pitch: Capture and share live samples onchain instantly without blockchain transaction fees. Why Hedera: Hedera's fixed sub-cent fees enable artists to record and distribute samples in real time without cost. Market: TAM $900M — live performance software | SAM $220M — live sampling tools | SOM $18M — free live sample sharing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Live Sampling" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Capture and share live samples onchain instantly without blockchain transaction fees. Discipline: Music & Sound Design (live-set sampling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees enable artists to record and distribute samples in real time without cost. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Live Sampling" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-social-jam-sessions-5 Title: Social Jam Sessions Theme: Music & Sound Design (music) · online collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Join real-time jam sessions with authenticated users and transparent blockchain session logs. Why Hedera: Magic Link email sign-ins simplify user sign-in and gasless session synchronization. Market: TAM $500M — online collaboration platforms | SAM $140M — live music collaboration | SOM $12M — onchain jam sessions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Social Jam Sessions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Join real-time jam sessions with authenticated users and transparent blockchain session logs. Discipline: Music & Sound Design (online collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins simplify user sign-in and gasless session synchronization. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Social Jam Sessions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-curated-sound-trails-6 Title: Curated Sound Trails Theme: Music & Sound Design (music) · music curation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and share onchain-curated playlists with gasless user interaction and proof of curation. Why Hedera: Hedera's fixed sub-cent fees allow playlist creation and endorsements without gas burdening fans. Market: TAM $2B — music streaming platforms | SAM $400M — playlist curation services | SOM $30M — gasless playlist tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Curated Sound Trails" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and share onchain-curated playlists with gasless user interaction and proof of curation. Discipline: Music & Sound Design (music curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees allow playlist creation and endorsements without gas burdening fans. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Curated Sound Trails" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-authentic-sample-provenance-7 Title: Authentic Sample Provenance Theme: Music & Sound Design (music) · sample authentication Hedera hook: Magic Link email wallet [wallet UX] Pitch: Verify sample origins onchain with free, user-friendly wallet onboarding to prevent piracy. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees reduce friction in authenticating sample ownership. Market: TAM $750M — sample licensing market | SAM $200M — sample authentication tech | SOM $14M — gasless provenance services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Authentic Sample Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify sample origins onchain with free, user-friendly wallet onboarding to prevent piracy. Discipline: Music & Sound Design (sample authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees reduce friction in authenticating sample ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Authentic Sample Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-synth-presets-8 Title: Onchain Synth Presets Theme: Music & Sound Design (music) · sound design presets Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute and monetize synth presets with gas-free transactions and easy social login. Why Hedera: Magic Link email sign-ins facilitate frictionless preset purchase and sharing without gas fees. Market: TAM $450M — synth preset market | SAM $95M — preset monetization | SOM $8M — gasless preset distribution ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Synth Presets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute and monetize synth presets with gas-free transactions and easy social login. Discipline: Music & Sound Design (sound design presets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins facilitate frictionless preset purchase and sharing without gas fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Synth Presets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-beat-battles-9 Title: Gasless Beat Battles Theme: Music & Sound Design (music) · music competitions Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host onchain beat battles allowing creators to compete and vote without paying blockchain fees. Why Hedera: Hedera's fixed sub-cent fees and seamless wallets enable mass participation without gas deterrents. Market: TAM $300M — online music contests | SAM $85M — beat battle platforms | SOM $7M — free competition hosting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Beat Battles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host onchain beat battles allowing creators to compete and vote without paying blockchain fees. Discipline: Music & Sound Design (music competitions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees and seamless wallets enable mass participation without gas deterrents. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Beat Battles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-collaborative-loop-vault-10 Title: Collaborative Loop Vault Theme: Music & Sound Design (music) · loop sharing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely share and build loop collections with zero gas cost and social wallet access. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable multiple collaborators to add loops frictionlessly. Market: TAM $600M — loop marketplaces | SAM $150M — collaborative loop tools | SOM $11M — gasless loop sharing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Loop Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely share and build loop collections with zero gas cost and social wallet access. Discipline: Music & Sound Design (loop sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable multiple collaborators to add loops frictionlessly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Loop Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-transparent-sample-licensing-11 Title: Transparent Sample Licensing Theme: Music & Sound Design (music) · sample rights Hedera hook: Magic Link email wallet [wallet UX] Pitch: License samples with onchain transparency and gasless user adoption via social login. Why Hedera: Magic Link email sign-ins reduce onboarding friction, Hedera's fixed sub-cent fees cover license actions gas-free. Market: TAM $1B — sample licensing market | SAM $270M — transparent licensing solutions | SOM $16M — gasless licensing platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Transparent Sample Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License samples with onchain transparency and gasless user adoption via social login. Discipline: Music & Sound Design (sample rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins reduce onboarding friction, Hedera's fixed sub-cent fees cover license actions gas-free. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Transparent Sample Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-instant-collab-contracts-12 Title: Instant Collab Contracts Theme: Music & Sound Design (music) · agreement automation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and sign collaboration agreements onchain instantly without gas fees or complex wallet setup. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees allow swift contract handling with minimal user effort. Market: TAM $4B — digital contract management | SAM $900M — music collaboration contracts | SOM $50M — gasless contract signing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Instant Collab Contracts" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and sign collaboration agreements onchain instantly without gas fees or complex wallet setup. Discipline: Music & Sound Design (agreement automation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees allow swift contract handling with minimal user effort. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Instant Collab Contracts" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-sound-design-nft-13 Title: Gasless Sound Design NFT Theme: Music & Sound Design (music) · NFT sound assets Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint and trade unique sound designs onchain with free wallet sign-ins and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-ins combined with Hedera's fixed sub-cent fees remove barriers for NFT sound creators and buyers. Market: TAM $500M — NFT music asset market | SAM $130M — sound design NFTs | SOM $9M — gasless NFT minting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Sound Design NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade unique sound designs onchain with free wallet sign-ins and Hedera's fixed sub-cent fees. Discipline: Music & Sound Design (NFT sound assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins combined with Hedera's fixed sub-cent fees remove barriers for NFT sound creators and buyers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Sound Design NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-mix-feedback-14 Title: Onchain Mix Feedback Theme: Music & Sound Design (music) · mix critique Hedera hook: Magic Link email wallet [wallet UX] Pitch: Receive verified community feedback on mixes with gasless voting and wallet authentication. Why Hedera: Hedera's fixed sub-cent fees ensure cost-free feedback submission and Magic Link email sign-in streamlines sign-in. Market: TAM $350M — online music feedback | SAM $90M — mix critique platforms | SOM $6M — gasless feedback solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Mix Feedback" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Receive verified community feedback on mixes with gasless voting and wallet authentication. Discipline: Music & Sound Design (mix critique). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees ensure cost-free feedback submission and Magic Link email sign-in streamlines sign-in. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Mix Feedback" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-sample-swap-15 Title: Gasless Sample Swap Theme: Music & Sound Design (music) · sample exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Swap samples peer-to-peer with instant, free onchain transactions and simple social login. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable smooth, fee-free sample trades. Market: TAM $800M — sample exchange market | SAM $210M — P2P music swaps | SOM $13M — gasless sample exchanges ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Sample Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Swap samples peer-to-peer with instant, free onchain transactions and simple social login. Discipline: Music & Sound Design (sample exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable smooth, fee-free sample trades. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Sample Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-live-set-proof-16 Title: Live Set Proof Theme: Music & Sound Design (music) · performance verification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Proof your live performances onchain with zero gas fees and social wallet ease. Why Hedera: Hedera's fixed sub-cent fees cover onchain proofing costs, Magic Link email sign-ins simplify authentication. Market: TAM $1B — live performance software | SAM $300M — performance verification tools | SOM $25M — gasless proof platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Live Set Proof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Proof your live performances onchain with zero gas fees and social wallet ease. Discipline: Music & Sound Design (performance verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees cover onchain proofing costs, Magic Link email sign-ins simplify authentication. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Live Set Proof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-loop-licensing-17 Title: Gasless Loop Licensing Theme: Music & Sound Design (music) · loop rights Hedera hook: Magic Link email wallet [wallet UX] Pitch: License loops onchain with frictionless onboarding and no blockchain transaction fees. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless licensing without gas concern. Market: TAM $650M — loop licensing market | SAM $180M — loop licensing tech | SOM $12M — gasless licensing apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Loop Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License loops onchain with frictionless onboarding and no blockchain transaction fees. Discipline: Music & Sound Design (loop rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless licensing without gas concern. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Loop Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-social-audio-credits-18 Title: Social Audio Credits Theme: Music & Sound Design (music) · microtransactions Hedera hook: Magic Link email wallet [wallet UX] Pitch: Tip and credit collaborators instantly with gasless onchain microtransactions via social login. Why Hedera: Hedera's fixed sub-cent fees reduce friction in micro-payments and Magic Link email sign-ins ease user adoption. Market: TAM $700M — music microtransaction market | SAM $160M — tip and credit platforms | SOM $10M — gasless tipping solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Social Audio Credits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tip and credit collaborators instantly with gasless onchain microtransactions via social login. Discipline: Music & Sound Design (microtransactions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees reduce friction in micro-payments and Magic Link email sign-ins ease user adoption. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Social Audio Credits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-synth-sharing-19 Title: Gasless Synth Sharing Theme: Music & Sound Design (music) · preset sharing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share and demo synthesizer presets with gas-free onchain transactions and social wallet integration. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees allow easy and free preset transfer. Market: TAM $400M — synth preset community | SAM $100M — preset sharing tools | SOM $7M — gasless synth sharing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Synth Sharing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share and demo synthesizer presets with gas-free onchain transactions and social wallet integration. Discipline: Music & Sound Design (preset sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees allow easy and free preset transfer. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Synth Sharing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-onchain-audio-stems-20 Title: Onchain Audio Stems Theme: Music & Sound Design (music) · stem distribution Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute high-quality audio stems with transparent ownership and zero gas fees. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees enable frictionless stem delivery and proof of provenance. Market: TAM $900M — stem marketplaces | SAM $250M — stem distribution platforms | SOM $20M — gasless stem sharing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Audio Stems" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute high-quality audio stems with transparent ownership and zero gas fees. Discipline: Music & Sound Design (stem distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees enable frictionless stem delivery and proof of provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Audio Stems" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-sample-challenges-21 Title: Gasless Sample Challenges Theme: Music & Sound Design (music) · creative contests Hedera hook: Magic Link email wallet [wallet UX] Pitch: Run onchain sample challenge contests with free user participation and wallet sign-in. Why Hedera: Hedera's fixed sub-cent fees remove gas cost barrier, Magic Link email sign-ins simplify onboarding. Market: TAM $350M — music challenge market | SAM $85M — sample contest platforms | SOM $7M — gasless creative contests ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Sample Challenges" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Run onchain sample challenge contests with free user participation and wallet sign-in. Discipline: Music & Sound Design (creative contests). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees remove gas cost barrier, Magic Link email sign-ins simplify onboarding. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Sample Challenges" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-decentralized-sound-workshops-22 Title: Decentralized Sound Workshops Theme: Music & Sound Design (music) · music education Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host interactive sound design workshops with onchain attendance proofs and no gas fees. Why Hedera: Magic Link email sign-ins streamline user authentication, Hedera's fixed sub-cent fees cover transaction costs gasless. Market: TAM $1.2B — music education tech | SAM $300M — sound workshop platforms | SOM $22M — gasless workshop hosting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Sound Workshops" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host interactive sound design workshops with onchain attendance proofs and no gas fees. Discipline: Music & Sound Design (music education). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins streamline user authentication, Hedera's fixed sub-cent fees cover transaction costs gasless. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Sound Workshops" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-collaborative-audio-nfts-23 Title: Collaborative Audio NFTs Theme: Music & Sound Design (music) · co-authored NFTs Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint multi-creator audio NFTs with transparent ownership and gasless transactions. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees ease multi-user minting processes without costs. Market: TAM $550M — audio NFT market | SAM $140M — collaborative NFT platforms | SOM $11M — gasless co-authored NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Audio NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint multi-creator audio NFTs with transparent ownership and gasless transactions. Discipline: Music & Sound Design (co-authored NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees ease multi-user minting processes without costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Audio NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-gasless-midi-trading-24 Title: Gasless MIDI Trading Theme: Music & Sound Design (music) · MIDI asset exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Trade MIDI files onchain instantly with social login and no blockchain fees. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees allow frictionless, cost-free MIDI trades. Market: TAM $450M — MIDI asset market | SAM $120M — MIDI trading platforms | SOM $9M — gasless MIDI exchange ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless MIDI Trading" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade MIDI files onchain instantly with social login and no blockchain fees. Discipline: Music & Sound Design (MIDI asset exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees allow frictionless, cost-free MIDI trades. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless MIDI Trading" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sample-provenance-vault-0 Title: Sample Provenance Vault Theme: Music & Sound Design (music) · sample library curation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and verify original samples for trusted reuse and licensing. Why Hedera: NFT provenance ensures unalterable origin tracking for authentic sample ownership. Market: TAM $4B — global sample marketplace | SAM $800M — curated sample libraries | SOM $150M — niche sample resale platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sample Provenance Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and verify original samples for trusted reuse and licensing. Discipline: Music & Sound Design (sample library curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures unalterable origin tracking for authentic sample ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sample Provenance Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-mix-snapshot-ledger-1 Title: Mix Snapshot Ledger Theme: Music & Sound Design (music) · mix version archiving Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique mix versions so producers prove original mixes over time. Why Hedera: HTS NFT tokens immutably link to mix versions, enabling verifiable mix lineage. Market: TAM $1.2B — music production software | SAM $300M — mixing and mastering studios | SOM $50M — indie producer tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Mix Snapshot Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique mix versions so producers prove original mixes over time. Discipline: Music & Sound Design (mix version archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens immutably link to mix versions, enabling verifiable mix lineage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Mix Snapshot Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-synth-patch-provenance-2 Title: Synth Patch Provenance Theme: Music & Sound Design (music) · synthesizer preset sharing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create certified original synth presets with owned provenance for resale or sharing. Why Hedera: NFTs verify authorship of preset files stored off-chain on IPFS. Market: TAM $700M — synth market | SAM $200M — preset marketplaces | SOM $40M — boutique synth users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Synth Patch Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create certified original synth presets with owned provenance for resale or sharing. Discipline: Music & Sound Design (synthesizer preset sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs verify authorship of preset files stored off-chain on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Synth Patch Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sample-remix-rights-3 Title: Sample Remix Rights Theme: Music & Sound Design (music) · remix licensing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint remixable sample NFTs granting verified usage and royalty rights to remixers. Why Hedera: Onchain NFT ownership proves and transfers remix rights transparently. Market: TAM $5B — music rights licensing | SAM $1B — remix licensing platforms | SOM $120M — small remix communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sample Remix Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint remixable sample NFTs granting verified usage and royalty rights to remixers. Discipline: Music & Sound Design (remix licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFT ownership proves and transfers remix rights transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sample Remix Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-live-loop-provenance-4 Title: Live Loop Provenance Theme: Music & Sound Design (music) · live performance loops Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Capture and mint unique live-loop sessions for verified creative ownership. Why Hedera: HTS NFT NFTs timestamp and link to live loop IPFS snapshots immutably. Market: TAM $2B — live performance tech | SAM $500M — loop creation tools | SOM $80M — live loop marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Live Loop Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Capture and mint unique live-loop sessions for verified creative ownership. Discipline: Music & Sound Design (live performance loops). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT NFTs timestamp and link to live loop IPFS snapshots immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Live Loop Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-virtual-instrument-tokens-5 Title: Virtual Instrument Tokens Theme: Music & Sound Design (music) · instrument sample packs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint exclusive sample pack NFTs with creator ownership and usage proofs. Why Hedera: NFTs provide authenticated proof of creation and distribution for sample packs. Market: TAM $3B — virtual instrument market | SAM $600M — sample pack sales | SOM $100M — exclusive sample pack niches ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Instrument Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint exclusive sample pack NFTs with creator ownership and usage proofs. Discipline: Music & Sound Design (instrument sample packs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide authenticated proof of creation and distribution for sample packs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Instrument Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sound-effect-provenance-6 Title: Sound Effect Provenance Theme: Music & Sound Design (music) · sound effect libraries Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Identify and authenticate original sound effect creators with minting onchain tokens. Why Hedera: Immutable token metadata links sound effects to verified creators. Market: TAM $1.5B — sound effects market | SAM $400M — licensed SFX libraries | SOM $60M — indie SFX creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound Effect Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Identify and authenticate original sound effect creators with minting onchain tokens. Discipline: Music & Sound Design (sound effect libraries). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable token metadata links sound effects to verified creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound Effect Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-composer-cue-tokens-7 Title: Composer Cue Tokens Theme: Music & Sound Design (music) · media scoring segments Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original music cue NFTs to prove composer ownership and license rights. Why Hedera: HTS NFT provenance tokens secure music cues for synchronization licensing. Market: TAM $1.8B — sync licensing market | SAM $450M — media scoring services | SOM $70M — indie composer platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Composer Cue Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original music cue NFTs to prove composer ownership and license rights. Discipline: Music & Sound Design (media scoring segments). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT provenance tokens secure music cues for synchronization licensing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Composer Cue Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-collaborative-track-tokens-8 Title: Collaborative Track Tokens Theme: Music & Sound Design (music) · co-creation tracking Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFTs representing joint music projects, tracking all contributors’ rights. Why Hedera: Onchain tokens prove shared ownership and IPFS links store collaborative files. Market: TAM $2.5B — collaborative music tools | SAM $700M — co-creation software | SOM $100M — emerging artist teams ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Track Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs representing joint music projects, tracking all contributors’ rights. Discipline: Music & Sound Design (co-creation tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokens prove shared ownership and IPFS links store collaborative files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Track Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-mastering-provenance-tags-9 Title: Mastering Provenance Tags Theme: Music & Sound Design (music) · audio mastering records Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint verifiable NFTs for mastered tracks to certify mastering sources and dates. Why Hedera: NFTs immutably link to mastered audio files and mastering engineer credentials. Market: TAM $900M — audio mastering market | SAM $250M — mastering studios | SOM $40M — freelance mastering engineers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Mastering Provenance Tags" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint verifiable NFTs for mastered tracks to certify mastering sources and dates. Discipline: Music & Sound Design (audio mastering records). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs immutably link to mastered audio files and mastering engineer credentials. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Mastering Provenance Tags" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-remix-chain-registry-10 Title: Remix Chain Registry Theme: Music & Sound Design (music) · remix lineage tracking Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Trace remix origins onchain by minting NFT tokens for each remix iteration. Why Hedera: HTS NFT tokens create transparent, provable remix trees with IPFS metadata. Market: TAM $4B — remix licensing market | SAM $1B — online remix services | SOM $150M — niche remix artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Remix Chain Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trace remix origins onchain by minting NFT tokens for each remix iteration. Discipline: Music & Sound Design (remix lineage tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens create transparent, provable remix trees with IPFS metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Remix Chain Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-songwriting-drafts-mint-11 Title: Songwriting Drafts Mint Theme: Music & Sound Design (music) · lyric version control Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs for songwriting drafts to prove original lyric authorship and evolution. Why Hedera: NFTs timestamp and store lyrical versions securely on IPFS with provenance. Market: TAM $1B — songwriting tools | SAM $300M — lyric collaboration apps | SOM $50M — independent songwriters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Songwriting Drafts Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs for songwriting drafts to prove original lyric authorship and evolution. Discipline: Music & Sound Design (lyric version control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs timestamp and store lyrical versions securely on IPFS with provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Songwriting Drafts Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-field-recording-mint-12 Title: Field Recording Mint Theme: Music & Sound Design (music) · ambient sound archives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Ownership minting for authentic field recordings archived with verifiable IPFS CIDs. Why Hedera: NFT provenance tokens preserve authenticity of raw audio captures. Market: TAM $600M — field recording tools | SAM $180M — ambient sound markets | SOM $30M — indie recordists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Field Recording Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Ownership minting for authentic field recordings archived with verifiable IPFS CIDs. Discipline: Music & Sound Design (ambient sound archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance tokens preserve authenticity of raw audio captures. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Field Recording Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sound-design-presets-13 Title: Sound Design Presets Theme: Music & Sound Design (music) · fx preset marketplaces Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint and trade unique sound design effect presets with creator provenance. Why Hedera: HTS NFT tokens link presets to creators, ensuring authenticity and resale. Market: TAM $1.1B — sound design software | SAM $350M — effect preset markets | SOM $60M — boutique sound designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound Design Presets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade unique sound design effect presets with creator provenance. Discipline: Music & Sound Design (fx preset marketplaces). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link presets to creators, ensuring authenticity and resale. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound Design Presets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-album-art-audio-tokens-14 Title: Album Art Audio Tokens Theme: Music & Sound Design (music) · integrated art & sound Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Combine album art and music provenance by minting unified NFT tokens. Why Hedera: Single provenance tokens immutable link both audio and visual IPFS assets. Market: TAM $2.2B — multimedia album markets | SAM $600M — artist merchandise | SOM $90M — indie visual musicians ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Album Art Audio Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Combine album art and music provenance by minting unified NFT tokens. Discipline: Music & Sound Design (integrated art & sound). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Single provenance tokens immutable link both audio and visual IPFS assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Album Art Audio Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-music-education-tokens-15 Title: Music Education Tokens Theme: Music & Sound Design (music) · tutorial authenticity Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique verified music tutorial NFTs proving instructor originality. Why Hedera: NFT provenance ensures authentic origin and ownership of tutorial content. Market: TAM $3B — online music education | SAM $700M — music tutorial platforms | SOM $120M — instructor niche markets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Music Education Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique verified music tutorial NFTs proving instructor originality. Discipline: Music & Sound Design (tutorial authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures authentic origin and ownership of tutorial content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Music Education Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-sound-asset-licensing-16 Title: Sound Asset Licensing Theme: Music & Sound Design (music) · asset marketplace Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Streamline sound asset licensing with minting provable NFT ownership tokens. Why Hedera: Onchain NFTs verify licenses and enable transparent royalty tracking. Market: TAM $5B — sound licensing economy | SAM $1.2B — digital asset stores | SOM $200M — independent sound creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sound Asset Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Streamline sound asset licensing with minting provable NFT ownership tokens. Discipline: Music & Sound Design (asset marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFTs verify licenses and enable transparent royalty tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sound Asset Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-genre-evolution-tokens-17 Title: Genre Evolution Tokens Theme: Music & Sound Design (music) · music style tracking Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs tagging tracks by genre and evolutionary lineage onchain. Why Hedera: HTS NFT tokens link tracks to genre metadata stored immutably on IPFS. Market: TAM $2B — music analytics tools | SAM $400M — genre classification software | SOM $70M — niche curator communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Genre Evolution Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs tagging tracks by genre and evolutionary lineage onchain. Discipline: Music & Sound Design (music style tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link tracks to genre metadata stored immutably on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Genre Evolution Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-interactive-audio-nfts-18 Title: Interactive Audio NFTs Theme: Music & Sound Design (music) · dynamic sound compositions Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint interactive music NFTs with provable ownership and modifiable IPFS content. Why Hedera: NFTs enable ownership and dynamic linking of mutable audio files. Market: TAM $1.7B — interactive media | SAM $450M — dynamic music platforms | SOM $80M — experimental musicians ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Audio NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint interactive music NFTs with provable ownership and modifiable IPFS content. Discipline: Music & Sound Design (dynamic sound compositions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs enable ownership and dynamic linking of mutable audio files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Audio NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-dj-set-provenance-19 Title: DJ Set Provenance Theme: Music & Sound Design (music) · live set archiving Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create certified NFTs of live DJ sets proving originality and performance dates. Why Hedera: HTS NFT tokens immutably link to archived set recordings on IPFS. Market: TAM $1.4B — DJ software market | SAM $350M — live set sales | SOM $50M — underground DJ communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DJ Set Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create certified NFTs of live DJ sets proving originality and performance dates. Discipline: Music & Sound Design (live set archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens immutably link to archived set recordings on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DJ Set Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-voice-sample-tokens-20 Title: Voice Sample Tokens Theme: Music & Sound Design (music) · vocal sample authentication Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs certifying original vocal samples for reuse and licensing. Why Hedera: NFT provenance ensures immutable vocal sample creator attribution. Market: TAM $900M — vocal sample market | SAM $250M — vocal sample packs | SOM $40M — indie vocalists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Voice Sample Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs certifying original vocal samples for reuse and licensing. Discipline: Music & Sound Design (vocal sample authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures immutable vocal sample creator attribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Voice Sample Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-podcast-sound-mint-21 Title: Podcast Sound Mint Theme: Music & Sound Design (music) · podcast audio authenticity Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint provenance NFTs for original podcast episodes guaranteeing ownership. Why Hedera: HTS NFT tokens link episodes immutably with metadata on IPFS. Market: TAM $3.2B — podcast production | SAM $800M — indie podcast creators | SOM $130M — podcast sound designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Podcast Sound Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint provenance NFTs for original podcast episodes guaranteeing ownership. Discipline: Music & Sound Design (podcast audio authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link episodes immutably with metadata on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Podcast Sound Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-film-score-provenance-22 Title: Film Score Provenance Theme: Music & Sound Design (music) · cinematic composition Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Certify original film scores by minting provenance NFTs tied to IPFS CIDs. Why Hedera: NFTs provide secure proof of score authorship and licensing. Market: TAM $2.7B — film music market | SAM $700M — scoring professionals | SOM $110M — indie film composers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Film Score Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Certify original film scores by minting provenance NFTs tied to IPFS CIDs. Discipline: Music & Sound Design (cinematic composition). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide secure proof of score authorship and licensing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Film Score Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-ambient-track-tokens-23 Title: Ambient Track Tokens Theme: Music & Sound Design (music) · ambient music archives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs for ambient compositions verifying original soundscapes. Why Hedera: HTS NFT tokens preserve ambient music provenance through IPFS storage. Market: TAM $800M — ambient music market | SAM $200M — online ambient platforms | SOM $30M — niche ambient artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Ambient Track Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs for ambient compositions verifying original soundscapes. Discipline: Music & Sound Design (ambient music archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens preserve ambient music provenance through IPFS storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Ambient Track Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA music-instrument-sample-provenance-24 Title: Instrument Sample Provenance Theme: Music & Sound Design (music) · instrumental sample rights Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFTs proving ownership of original instrument sample recordings. Why Hedera: NFT provenance tokens securely link samples to creator IPFS metadata. Market: TAM $1.5B — instrument sample libraries | SAM $400M — instrument sampling services | SOM $70M — boutique instrument sample sellers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Instrument Sample Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs proving ownership of original instrument sample recordings. Discipline: Music & Sound Design (instrumental sample rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance tokens securely link samples to creator IPFS metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Instrument Sample Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Photography photographers, photo editors, photojournalists ============================================================================== ------------------------------------------------------------------------------ IDEA photography-immutable-photo-rights-0 Title: Immutable Photo Rights Theme: Photography (photography) · copyright registry Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely register photo ownership and licensing rights on-chain with immutable proof. Why Hedera: Hedera testnet smart contracts ensure tamper-proof, transparent ownership records accessible worldwide. Market: TAM $2.4B — global photo software market | SAM $500M — photo licensing software segment | SOM $50M — professional photographers licensing their works ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Immutable Photo Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely register photo ownership and licensing rights on-chain with immutable proof. Discipline: Photography (copyright registry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure tamper-proof, transparent ownership records accessible worldwide. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Immutable Photo Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-photo-proof-1 Title: Onchain Photo Proof Theme: Photography (photography) · image authenticity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Embed verifiable metadata on Hedera testnet to prove photo authenticity and timestamp creation. Why Hedera: Smart contracts provide trustless, verifiable timestamping and metadata anchoring. Market: TAM $1.2B — photo verification tools market | SAM $300M — digital forensics software | SOM $30M — photojournalism authenticity tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Photo Proof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Embed verifiable metadata on Hedera testnet to prove photo authenticity and timestamp creation. Discipline: Photography (image authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide trustless, verifiable timestamping and metadata anchoring. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Photo Proof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-smart-photo-royalties-2 Title: Smart Photo Royalties Theme: Photography (photography) · royalty automation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate and transparently distribute photo royalties directly via Hedera testnet smart contracts. Why Hedera: Smart contracts enable automatic, fair, and transparent royalty payments without intermediaries. Market: TAM $2.4B — photo software market | SAM $600M — royalty management software | SOM $60M — professional photographers seeking automated royalties ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Smart Photo Royalties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate and transparently distribute photo royalties directly via Hedera testnet smart contracts. Discipline: Photography (royalty automation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable automatic, fair, and transparent royalty payments without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Smart Photo Royalties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-collaborative-edits-chain-3 Title: Collaborative Edits Chain Theme: Photography (photography) · collaborative editing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track photo edit histories and collaborators immutably on Hedera testnet for transparent teamwork. Why Hedera: Blockchain tracks and preserves every edit and contributor without central control. Market: TAM $2B — photo editing tools market | SAM $400M — collaborative editing software | SOM $40M — photo editors in teams ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Edits Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track photo edit histories and collaborators immutably on Hedera testnet for transparent teamwork. Discipline: Photography (collaborative editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Blockchain tracks and preserves every edit and contributor without central control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Edits Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-decentralized-portfolios-4 Title: Decentralized Portfolios Theme: Photography (photography) · photographer profiles Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create and showcase decentralized photography portfolios verified on Hedera testnet smart contracts. Why Hedera: Onchain portfolios guarantee authenticity and censorship resistance for photographers’ work. Market: TAM $2.4B — photo software market | SAM $350M — portfolio hosting platforms | SOM $35M — pro photographers showcasing work ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Portfolios" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and showcase decentralized photography portfolios verified on Hedera testnet smart contracts. Discipline: Photography (photographer profiles). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain portfolios guarantee authenticity and censorship resistance for photographers’ work. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Portfolios" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-usage-tracking-5 Title: Onchain Usage Tracking Theme: Photography (photography) · photo usage monitoring Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and record photo usage and distribution on-chain in real time for accurate licensing. Why Hedera: Hedera testnet smart contracts enable decentralized, verifiable tracking immutable by any party. Market: TAM $1.8B — photo tracking software market | SAM $450M — monitoring/licensing enforcement tools | SOM $45M — photographers monitoring usage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Usage Tracking" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and record photo usage and distribution on-chain in real time for accurate licensing. Discipline: Photography (photo usage monitoring). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable decentralized, verifiable tracking immutable by any party. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Usage Tracking" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-nft-prints-marketplace-6 Title: NFT Prints Marketplace Theme: Photography (photography) · photo prints trading Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Sell limited edition photo prints linked to NFTs minted and verified on Hedera testnet. Why Hedera: Hedera testnet contracts ensure scarcity and authenticity of photo print NFTs for collectors. Market: TAM $3B — global NFT market | SAM $400M — photography NFTs segment | SOM $40M — photographers selling NFT prints ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Prints Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sell limited edition photo prints linked to NFTs minted and verified on Hedera testnet. Discipline: Photography (photo prints trading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts ensure scarcity and authenticity of photo print NFTs for collectors. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Prints Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-proof-of-capture-7 Title: Proof of Capture Theme: Photography (photography) · capture verification Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Validate photo capture location and time on-chain instantly using Hedera testnet smart contracts. Why Hedera: Immutable timestamp and geodata registration ensures real-time proof of photo origins. Market: TAM $2.4B — photo software market | SAM $250M — photo verification submarket | SOM $25M — photojournalists needing capture proof ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Proof of Capture" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Validate photo capture location and time on-chain instantly using Hedera testnet smart contracts. Discipline: Photography (capture verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable timestamp and geodata registration ensures real-time proof of photo origins. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Proof of Capture" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-smart-rights-marketplace-8 Title: Smart Rights Marketplace Theme: Photography (photography) · licensing exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralized marketplace for buying and selling photo licenses managed by smart contracts. Why Hedera: Smart contracts automate licensing terms and transfers securely and transparently. Market: TAM $2.4B — photo software market | SAM $600M — digital licensing platforms | SOM $50M — photographers monetizing licenses ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Smart Rights Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralized marketplace for buying and selling photo licenses managed by smart contracts. Discipline: Photography (licensing exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate licensing terms and transfers securely and transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Smart Rights Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-verified-edits-9 Title: Chain Verified Edits Theme: Photography (photography) · edit validation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Verify authenticity and sequence of photo edits on-chain to certify editorial integrity. Why Hedera: Blockchain immutably validates editing steps, preventing manipulation or forgery. Market: TAM $1.5B — photo editing market | SAM $350M — verification tools | SOM $35M — photojournalists ensuring edit transparency ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain Verified Edits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify authenticity and sequence of photo edits on-chain to certify editorial integrity. Discipline: Photography (edit validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Blockchain immutably validates editing steps, preventing manipulation or forgery. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain Verified Edits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-decentralized-feedback-10 Title: Decentralized Feedback Theme: Photography (photography) · community critique Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Gather verified community feedback on photos via Hedera testnet-based smart contract voting. Why Hedera: Smart contracts guarantee transparent, tamper-proof, and fair community voting on photos. Market: TAM $1.2B — community-driven photo platforms | SAM $250M — photo critique marketplaces | SOM $25M — photographers seeking authentic feedback ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Feedback" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Gather verified community feedback on photos via Hedera testnet-based smart contract voting. Discipline: Photography (community critique). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts guarantee transparent, tamper-proof, and fair community voting on photos. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Feedback" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-provenance-logs-11 Title: Chain Provenance Logs Theme: Photography (photography) · provenance tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track entire lifecycle and ownership history of photos on Hedera testnet for provenance assurance. Why Hedera: Smart contracts provide an immutable chain of custody for photos. Market: TAM $2.4B — photo software market | SAM $400M — provenance tracking tools | SOM $40M — photo licensing professionals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain Provenance Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track entire lifecycle and ownership history of photos on Hedera testnet for provenance assurance. Discipline: Photography (provenance tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide an immutable chain of custody for photos. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain Provenance Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-photo-challenges-12 Title: Onchain Photo Challenges Theme: Photography (photography) · contest management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Host transparent and decentralized photo contests with onchain submission and voting. Why Hedera: Smart contracts ensure fairness and transparency of submissions and awards. Market: TAM $800M — photo contest platforms | SAM $200M — online contest management | SOM $20M — photographers entering contests ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Photo Challenges" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host transparent and decentralized photo contests with onchain submission and voting. Discipline: Photography (contest management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts ensure fairness and transparency of submissions and awards. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Photo Challenges" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-verified-client-contracts-13 Title: Verified Client Contracts Theme: Photography (photography) · client agreements Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Draft and execute photo service contracts on Hedera testnet smart contracts, reducing disputes. Why Hedera: Onchain contracts provide immutable, verifiable proof of agreed terms. Market: TAM $2.4B — photo services market | SAM $500M — client contract software | SOM $50M — photographers managing client agreements ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Verified Client Contracts" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Draft and execute photo service contracts on Hedera testnet smart contracts, reducing disputes. Discipline: Photography (client agreements). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain contracts provide immutable, verifiable proof of agreed terms. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Verified Client Contracts" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-tip-jar-integration-14 Title: Tip Jar Integration Theme: Photography (photography) · microdonations Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable fans to tip photographers instantly via Hedera testnet smart contract wallets. Why Hedera: Smart contracts facilitate direct, trustless transfer of microdonations with low fees. Market: TAM $1B — creator monetization software | SAM $250M — tipping platforms | SOM $25M — photographers earning fan support ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tip Jar Integration" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable fans to tip photographers instantly via Hedera testnet smart contract wallets. Discipline: Photography (microdonations). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts facilitate direct, trustless transfer of microdonations with low fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tip Jar Integration" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-linked-exif-15 Title: Chain-Linked EXIF Theme: Photography (photography) · metadata anchoring Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Anchor and verify photo EXIF metadata on Hedera testnet to prevent tampering and ensure trust. Why Hedera: Smart contracts store immutable metadata fingerprints for reliable verification. Market: TAM $2.4B — photo software market | SAM $400M — photo metadata tools | SOM $40M — photo editors verifying authenticity ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Linked EXIF" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Anchor and verify photo EXIF metadata on Hedera testnet to prevent tampering and ensure trust. Discipline: Photography (metadata anchoring). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts store immutable metadata fingerprints for reliable verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Linked EXIF" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-photo-licensing-16 Title: Onchain Photo Licensing Theme: Photography (photography) · license contracts Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create customizable photo license agreements executed automatically via Hedera testnet contracts. Why Hedera: Smart contracts automate enforcement and execution of license terms trustlessly. Market: TAM $2.4B — photo software market | SAM $600M — licensing management | SOM $60M — photographers licensing digital content ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Photo Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create customizable photo license agreements executed automatically via Hedera testnet contracts. Discipline: Photography (license contracts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate enforcement and execution of license terms trustlessly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Photo Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-decentralized-photo-auctions-17 Title: Decentralized Photo Auctions Theme: Photography (photography) · auction platform Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Run transparent auctions for rare photos secured and settled using Hedera testnet smart contracts. Why Hedera: Blockchain guarantees fair bidding and transparent settlement without intermediaries. Market: TAM $2B — photo sales platforms | SAM $450M — auction software market | SOM $45M — photographers selling rare works ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Photo Auctions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Run transparent auctions for rare photos secured and settled using Hedera testnet smart contracts. Discipline: Photography (auction platform). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Blockchain guarantees fair bidding and transparent settlement without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Photo Auctions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-smart-watermark-registry-18 Title: Smart Watermark Registry Theme: Photography (photography) · watermark management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Register, track, and enforce photo watermarks rights via onchain smart contracts. Why Hedera: Smart contracts provide provable ownership and watermark integrity enforcement. Market: TAM $1.5B — watermarking software | SAM $300M — rights enforcement tools | SOM $30M — photographers protecting images ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Smart Watermark Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Register, track, and enforce photo watermarks rights via onchain smart contracts. Discipline: Photography (watermark management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide provable ownership and watermark integrity enforcement. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Smart Watermark Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-backed-photo-grants-19 Title: Chain-Backed Photo Grants Theme: Photography (photography) · funding management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage and distribute photography grant funds transparently on Hedera testnet smart contracts. Why Hedera: Smart contracts ensure fair, traceable, and automated grant distribution. Market: TAM $500M — photography grants market | SAM $150M — grant disbursement software | SOM $15M — photographers receiving funds ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Backed Photo Grants" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and distribute photography grant funds transparently on Hedera testnet smart contracts. Discipline: Photography (funding management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts ensure fair, traceable, and automated grant distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Backed Photo Grants" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-model-releases-20 Title: Onchain Model Releases Theme: Photography (photography) · legal documentation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Digitally sign and store model release forms on Hedera testnet blockchain for immutable proof. Why Hedera: Smart contracts provide tamper-proof, verifiable legal agreements. Market: TAM $2.4B — photo services market | SAM $350M — release management tools | SOM $35M — photographers managing releases ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Model Releases" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitally sign and store model release forms on Hedera testnet blockchain for immutable proof. Discipline: Photography (legal documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide tamper-proof, verifiable legal agreements. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Model Releases" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-photo-data-monetization-21 Title: Photo Data Monetization Theme: Photography (photography) · data marketplace Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Monetize photo metadata and usage data transparently via Hedera testnet smart contracts. Why Hedera: Smart contracts enable secure, permissioned data sales without intermediaries. Market: TAM $1.8B — photo data analytics market | SAM $400M — data marketplaces | SOM $40M — photographers selling insights ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Photo Data Monetization" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Monetize photo metadata and usage data transparently via Hedera testnet smart contracts. Discipline: Photography (data marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable secure, permissioned data sales without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Photo Data Monetization" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-based-mentorship-22 Title: Chain-based Mentorship Theme: Photography (photography) · educational matching Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Match photography mentors and mentees with verified contracts and session records onchain. Why Hedera: Smart contracts track commitments and ensure trust between mentors and learners. Market: TAM $700M — photography education market | SAM $200M — mentorship platforms | SOM $20M — photographers accessing mentorship ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-based Mentorship" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Match photography mentors and mentees with verified contracts and session records onchain. Discipline: Photography (educational matching). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts track commitments and ensure trust between mentors and learners. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-based Mentorship" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-photo-presets-23 Title: Onchain Photo Presets Theme: Photography (photography) · preset licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create, license, and verify photo editing presets with immutable smart contract records. Why Hedera: Smart contracts prove preset originality and license transfers transparently. Market: TAM $1.5B — photo editing tools market | SAM $300M — preset marketplaces | SOM $30M — photographers buying presets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Photo Presets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create, license, and verify photo editing presets with immutable smart contract records. Discipline: Photography (preset licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts prove preset originality and license transfers transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Photo Presets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-decentralized-event-coverage-24 Title: Decentralized Event Coverage Theme: Photography (photography) · crowdsourced photo event Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Coordinate and verify crowdsourced event photography via Hedera testnet smart contracts for reliability. Why Hedera: Blockchain ensures verifiable contributions and transparent reward distribution. Market: TAM $1B — event photography market | SAM $250M — crowdsourced platforms | SOM $25M — event photographers collaborating ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Decentralized Event Coverage" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Coordinate and verify crowdsourced event photography via Hedera testnet smart contracts for reliability. Discipline: Photography (crowdsourced photo event). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Blockchain ensures verifiable contributions and transparent reward distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Decentralized Event Coverage" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chainframe-archive-0 Title: ChainFrame Archive Theme: Photography (photography) · photo archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely store and verify photo archives with immutable IPFS-backed metadata for trusted provenance. Why Hedera: Pinata JWT ensures permanent, tamper-proof image storage with decentralized CID referencing. Market: TAM $2.4B — global photo software market | SAM $350M — professional photo archiving tools | SOM $45M — blockchain-verified photo archive users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChainFrame Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and verify photo archives with immutable IPFS-backed metadata for trusted provenance. Discipline: Photography (photo archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures permanent, tamper-proof image storage with decentralized CID referencing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChainFrame Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-pinphoto-journal-1 Title: PinPhoto Journal Theme: Photography (photography) · photojournalism documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Verify and timestamp news photos on IPFS to ensure authentic, unaltered media for journalism. Why Hedera: Pinning images with JWT certifies and preserves photojournalistic integrity via immutable CID links. Market: TAM $2.4B — global photo software market | SAM $500M — photojournalism editing platforms | SOM $70M — verified news photo users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PinPhoto Journal" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify and timestamp news photos on IPFS to ensure authentic, unaltered media for journalism. Discipline: Photography (photojournalism documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning images with JWT certifies and preserves photojournalistic integrity via immutable CID links. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PinPhoto Journal" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-colorchain-palette-2 Title: ColorChain Palette Theme: Photography (photography) · color grading Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Save and share color grading presets and images as permanent IPFS assets for consistent workflow. Why Hedera: Pinata JWT securely stores color data and images ensuring consistent cross-device access via CID. Market: TAM $2.4B — global photo software market | SAM $200M — color grading tool market | SOM $30M — decentralized preset sharing users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorChain Palette" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save and share color grading presets and images as permanent IPFS assets for consistent workflow. Discipline: Photography (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT securely stores color data and images ensuring consistent cross-device access via CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorChain Palette" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-metaframe-vault-3 Title: MetaFrame Vault Theme: Photography (photography) · metadata embedding Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Embed and immutably store photo metadata on IPFS to preserve creator rights and data integrity. Why Hedera: IPFS pinning via JWT guarantees permanent metadata storage linked to each image CID. Market: TAM $2.4B — global photo software market | SAM $300M — metadata management tools | SOM $40M — photographers using immutable metadata ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MetaFrame Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Embed and immutably store photo metadata on IPFS to preserve creator rights and data integrity. Discipline: Photography (metadata embedding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS pinning via JWT guarantees permanent metadata storage linked to each image CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MetaFrame Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-snapshot-ledger-4 Title: Snapshot Ledger Theme: Photography (photography) · photo licensing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create a decentralized ledger of photo licenses pinned to IPFS for transparent usage rights. Why Hedera: Pinata JWT pins licensing info securely with images, enabling immutable proof of rights. Market: TAM $2.4B — global photo software market | SAM $400M — photo licensing platforms | SOM $60M — blockchain-enabled license users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Snapshot Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a decentralized ledger of photo licenses pinned to IPFS for transparent usage rights. Discipline: Photography (photo licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT pins licensing info securely with images, enabling immutable proof of rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Snapshot Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-focusproof-network-5 Title: FocusProof Network Theme: Photography (photography) · focus tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Record lens focus data alongside images on IPFS for verifiable sharpness and authenticity. Why Hedera: Pinata JWT uploads link images with focus metadata immutably on IPFS via permanent CID. Market: TAM $2.4B — global photo software market | SAM $150M — advanced camera data tools | SOM $20M — focus metadata blockchain users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FocusProof Network" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record lens focus data alongside images on IPFS for verifiable sharpness and authenticity. Discipline: Photography (focus tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT uploads link images with focus metadata immutably on IPFS via permanent CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FocusProof Network" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-exposuretrace-6 Title: ExposureTrace Theme: Photography (photography) · exposure analysis Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store exposure settings and images together on IPFS to improve reproducibility and learning. Why Hedera: Pinning exposure data with PinataJWT creates unalterable records linked to each image CID. Market: TAM $2.4B — global photo software market | SAM $120M — exposure control software market | SOM $18M — photographers tracking data onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ExposureTrace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store exposure settings and images together on IPFS to improve reproducibility and learning. Discipline: Photography (exposure analysis). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning exposure data with PinataJWT creates unalterable records linked to each image CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ExposureTrace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-momentmap-7 Title: MomentMap Theme: Photography (photography) · location tagging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin geotagged photos on IPFS to create a permanent, verifiable map of photographic moments. Why Hedera: Pinata JWT ensures location data and images remain bound immutably via IPFS CID. Market: TAM $2.4B — global photo software market | SAM $250M — photo location services | SOM $35M — decentralized geo-tagging users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MomentMap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin geotagged photos on IPFS to create a permanent, verifiable map of photographic moments. Discipline: Photography (location tagging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures location data and images remain bound immutably via IPFS CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MomentMap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-collabochain-studio-8 Title: CollaboChain Studio Theme: Photography (photography) · collaborative editing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Share locked versions of photos and edits on IPFS for trustable collaborative workflows. Why Hedera: Pinning edits and base images via JWT creates permanent, shared CID histories. Market: TAM $2.4B — global photo software market | SAM $300M — collaborative photo editing tools | SOM $40M — decentralized collaborative editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CollaboChain Studio" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share locked versions of photos and edits on IPFS for trustable collaborative workflows. Discipline: Photography (collaborative editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning edits and base images via JWT creates permanent, shared CID histories. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CollaboChain Studio" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-nftrography-gallery-9 Title: NFTrography Gallery Theme: Photography (photography) · photo NFTs Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Mint and pin unique photographic art on IPFS to guarantee immutable ownership and provenance. Why Hedera: Pinata JWT uploads ensure permanent IPFS storage of NFT images and metadata. Market: TAM $2.4B — global photo software market | SAM $600M — NFT art platforms | SOM $80M — photographers minting NFTs securely ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFTrography Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and pin unique photographic art on IPFS to guarantee immutable ownership and provenance. Discipline: Photography (photo NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT uploads ensure permanent IPFS storage of NFT images and metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFTrography Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-proofshot-certify-10 Title: ProofShot Certify Theme: Photography (photography) · image authentication Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Authenticate original photos by pinning hashes and images on IPFS for proof of authenticity. Why Hedera: Pinata JWT enables immutable CID-based proof linking photo and hash data securely. Market: TAM $2.4B — global photo software market | SAM $280M — photo verification solutions | SOM $35M — authenticated photo users onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ProofShot Certify" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate original photos by pinning hashes and images on IPFS for proof of authenticity. Discipline: Photography (image authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT enables immutable CID-based proof linking photo and hash data securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ProofShot Certify" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chronocapture-log-11 Title: ChronoCapture Log Theme: Photography (photography) · time lapse Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin time-lapse image sequences on IPFS to preserve and share permanent chronological photo stories. Why Hedera: Pinning sequences via JWT anchors time-ordered images immutably through unique CIDs. Market: TAM $2.4B — global photo software market | SAM $100M — time-lapse video and photo tools | SOM $15M — time-lapse blockchain storage users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChronoCapture Log" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin time-lapse image sequences on IPFS to preserve and share permanent chronological photo stories. Discipline: Photography (time lapse). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning sequences via JWT anchors time-ordered images immutably through unique CIDs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChronoCapture Log" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-lenslegacy-vault-12 Title: LensLegacy Vault Theme: Photography (photography) · camera data archival Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store detailed camera sensor data with images on IPFS to analyze vintage gear performance over time. Why Hedera: Pinata JWT pins sensor metadata and images immutably with verifiable CID links. Market: TAM $2.4B — global photo software market | SAM $90M — camera data analysis tools | SOM $12M — archival users with onchain sensor data ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LensLegacy Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store detailed camera sensor data with images on IPFS to analyze vintage gear performance over time. Discipline: Photography (camera data archival). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT pins sensor metadata and images immutably with verifiable CID links. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LensLegacy Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-frameswap-marketplace-13 Title: FrameSwap Marketplace Theme: Photography (photography) · photo asset exchange Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Trade and pin licensed photo assets on IPFS to enable decentralized, tamper-proof exchanges. Why Hedera: Pinata JWT guarantees permanent storage and proof of ownership via IPFS CID. Market: TAM $2.4B — global photo software market | SAM $450M — digital photo asset marketplaces | SOM $55M — decentralized asset exchange users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameSwap Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade and pin licensed photo assets on IPFS to enable decentralized, tamper-proof exchanges. Discipline: Photography (photo asset exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT guarantees permanent storage and proof of ownership via IPFS CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameSwap Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-clipstatic-cdn-14 Title: ClipStatic CDN Theme: Photography (photography) · image delivery Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Distribute high-performance static photo assets pinned on IPFS for reliable global delivery. Why Hedera: Pinata JWT ensures permanent CID anchoring for scalable decentralized hosting. Market: TAM $2.4B — global photo software market | SAM $350M — photo content delivery networks | SOM $40M — IPFS-powered delivery users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipStatic CDN" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute high-performance static photo assets pinned on IPFS for reliable global delivery. Discipline: Photography (image delivery). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures permanent CID anchoring for scalable decentralized hosting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipStatic CDN" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-tokenizelight-15 Title: TokenizeLight Theme: Photography (photography) · rights management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create tokens linked to photos pinned on IPFS for secure, transparent rights and royalty tracking. Why Hedera: Pinata JWT creates durable IPFS records essential for tokenized royalty systems. Market: TAM $2.4B — global photo software market | SAM $400M — digital rights management | SOM $50M — photographer tokenization users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TokenizeLight" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create tokens linked to photos pinned on IPFS for secure, transparent rights and royalty tracking. Discipline: Photography (rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT creates durable IPFS records essential for tokenized royalty systems. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TokenizeLight" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-hdr-pinboard-16 Title: HDR Pinboard Theme: Photography (photography) · high dynamic range Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin multi-exposure HDR image sets on IPFS to ensure permanent, verifiable combination assets. Why Hedera: Pinning HDR frames via JWT creates immutable grouped CID references for complete sets. Market: TAM $2.4B — global photo software market | SAM $130M — HDR editing suites | SOM $17M — blockchain HDR workflow users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "HDR Pinboard" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin multi-exposure HDR image sets on IPFS to ensure permanent, verifiable combination assets. Discipline: Photography (high dynamic range). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning HDR frames via JWT creates immutable grouped CID references for complete sets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "HDR Pinboard" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-cropchain-editor-17 Title: CropChain Editor Theme: Photography (photography) · image cropping Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Save crop presets and cropped images pinned on IPFS for exact reproducibility and sharing. Why Hedera: Pinata JWT securely stores crop data alongside images with permanent CID linkage. Market: TAM $2.4B — global photo software market | SAM $150M — photo editing presets market | SOM $18M — immutable crop sharing users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CropChain Editor" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save crop presets and cropped images pinned on IPFS for exact reproducibility and sharing. Discipline: Photography (image cropping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT securely stores crop data alongside images with permanent CID linkage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CropChain Editor" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-pixelproof-sharing-18 Title: PixelProof Sharing Theme: Photography (photography) · image watermarking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin watermarked photos and proofs on IPFS to prevent unauthorized reuse with traceable CID. Why Hedera: Pinata JWT embeds watermarks with IPFS pins guaranteeing permanent, referenceable images. Market: TAM $2.4B — global photo software market | SAM $220M — watermark and proofing tools | SOM $28M — onchain watermark users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PixelProof Sharing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin watermarked photos and proofs on IPFS to prevent unauthorized reuse with traceable CID. Discipline: Photography (image watermarking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT embeds watermarks with IPFS pins guaranteeing permanent, referenceable images. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PixelProof Sharing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-filterchain-exchange-19 Title: FilterChain Exchange Theme: Photography (photography) · filter sharing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Publish photography filters and their sample images pinned on IPFS for permanent decentralized sharing. Why Hedera: Pinning filters and examples with Pinata JWT ensures immutable accessible filter assets. Market: TAM $2.4B — global photo software market | SAM $160M — photo filter marketplaces | SOM $22M — decentralized filter sharing users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FilterChain Exchange" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Publish photography filters and their sample images pinned on IPFS for permanent decentralized sharing. Discipline: Photography (filter sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning filters and examples with Pinata JWT ensures immutable accessible filter assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FilterChain Exchange" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-storyframe-ledger-20 Title: StoryFrame Ledger Theme: Photography (photography) · photo storytelling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin photo-story manifests on IPFS to secure multi-image narratives with permanent provenance. Why Hedera: JWT pinning creates immutable multi-photo linked manifests referenced by unique CIDs. Market: TAM $2.4B — global photo software market | SAM $280M — photo story creation tools | SOM $33M — onchain storytelling users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryFrame Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin photo-story manifests on IPFS to secure multi-image narratives with permanent provenance. Discipline: Photography (photo storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: JWT pinning creates immutable multi-photo linked manifests referenced by unique CIDs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryFrame Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-depthmap-chain-21 Title: DepthMap Chain Theme: Photography (photography) · 3D photography Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin depth maps and photos together on IPFS to preserve immersive photo data immutably. Why Hedera: Pinata JWT links image and depth layers securely with permanent CID storage. Market: TAM $2.4B — global photo software market | SAM $110M — 3D and depth photo tools | SOM $14M — blockchain 3D photo users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DepthMap Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin depth maps and photos together on IPFS to preserve immersive photo data immutably. Discipline: Photography (3D photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT links image and depth layers securely with permanent CID storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DepthMap Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-framesplit-archive-22 Title: FrameSplit Archive Theme: Photography (photography) · photo segmentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin segmented photo layers on IPFS for permanent storage and collaborative recombination. Why Hedera: Pinata JWT uploads ensure immutable access to each segmented layer via unique CID. Market: TAM $2.4B — global photo software market | SAM $130M — segmentation and masking tools | SOM $16M — onchain layered photo users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameSplit Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin segmented photo layers on IPFS for permanent storage and collaborative recombination. Discipline: Photography (photo segmentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT uploads ensure immutable access to each segmented layer via unique CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameSplit Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-batchpin-manager-23 Title: BatchPin Manager Theme: Photography (photography) · bulk image processing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and manage large photo batches on IPFS with JWT to streamline permanent storage workflows. Why Hedera: Pinata JWT supports bulk pinning with reliable CID management for large photo sets. Market: TAM $2.4B — global photo software market | SAM $400M — bulk photo management software | SOM $50M — professional batch pinning users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "BatchPin Manager" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and manage large photo batches on IPFS with JWT to streamline permanent storage workflows. Discipline: Photography (bulk image processing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT supports bulk pinning with reliable CID management for large photo sets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "BatchPin Manager" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-mirrorframe-backup-24 Title: MirrorFrame Backup Theme: Photography (photography) · image backup Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create decentralized backups of photos pinned on IPFS to protect against data loss permanently. Why Hedera: Pinata JWT offers permanent, redundant IPFS storage for safe off-site photo backup. Market: TAM $2.4B — global photo software market | SAM $300M — photo backup solutions | SOM $38M — decentralized photo backup users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MirrorFrame Backup" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create decentralized backups of photos pinned on IPFS to protect against data loss permanently. Discipline: Photography (image backup). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT offers permanent, redundant IPFS storage for safe off-site photo backup. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MirrorFrame Backup" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gasless-gallery-0 Title: Gasless Gallery Theme: Photography (photography) · photo sharing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Seamlessly share photos onchain with no gas fees for your audience and collaborators. Why Hedera: Hedera's fixed sub-cent fees remove gas friction, enabling smooth social sharing with Magic Link email sign-ins. Market: TAM $2.4B — global photo software market | SAM $600M — photo sharing and social platforms | SOM $30M — early adopters integrating onchain sharing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Seamlessly share photos onchain with no gas fees for your audience and collaborators. Discipline: Photography (photo sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees remove gas friction, enabling smooth social sharing with Magic Link email sign-ins. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-nft-photo-stories-1 Title: NFT Photo Stories Theme: Photography (photography) · photojournalism Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and share onchain photo stories with built-in provenance and no wallet setup hassle. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable easy entry for photojournalists. Market: TAM $2.4B — photo software plus NFT art markets | SAM $400M — NFT photojournalism tools | SOM $20M — pro photojournalists using NFT workflows ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Photo Stories" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and share onchain photo stories with built-in provenance and no wallet setup hassle. Discipline: Photography (photojournalism). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable easy entry for photojournalists. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Photo Stories" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chaincolor-study-2 Title: ChainColor Study Theme: Photography (photography) · color grading Hedera hook: Magic Link email wallet [wallet UX] Pitch: Save and share color grading presets onchain without spending gas or complex wallet setup. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees makes onchain preset sharing effortless. Market: TAM $2.4B — color grading and photo editing | SAM $500M — color grading presets market | SOM $15M — social sharing of presets onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChainColor Study" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save and share color grading presets onchain without spending gas or complex wallet setup. Discipline: Photography (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees makes onchain preset sharing effortless. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChainColor Study" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gas-free-prints-order-3 Title: Gas-Free Prints Order Theme: Photography (photography) · print ordering Hedera hook: Magic Link email wallet [wallet UX] Pitch: Order professional photo prints onchain with gasless transactions for instant confirmation. Why Hedera: Hedera's fixed sub-cent fees ensure print orders occur smoothly without user gas payments. Market: TAM $2.4B — photo software and print services | SAM $700M — pro photo print ordering systems | SOM $25M — onchain print order early users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Prints Order" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Order professional photo prints onchain with gasless transactions for instant confirmation. Discipline: Photography (print ordering). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees ensure print orders occur smoothly without user gas payments. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Prints Order" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-privy-photo-bounties-4 Title: the embedded wallet Photo Bounties Theme: Photography (photography) · community curation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create gasless, onchain photo curation challenges with built-in rewards and easy entry. Why Hedera: Magic Link email sign-ins support Hedera's fixed sub-cent fees for frictionless community-driven contests. Market: TAM $2.4B — photo community platforms | SAM $350M — photo curation and contest markets | SOM $10M — gasless onchain photo bounty participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Photo Bounties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create gasless, onchain photo curation challenges with built-in rewards and easy entry. Discipline: Photography (community curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins support Hedera's fixed sub-cent fees for frictionless community-driven contests. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Photo Bounties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-instant-rights-ledger-5 Title: Instant Rights Ledger Theme: Photography (photography) · rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage photo licensing rights transparently onchain with no gas fees for creators. Why Hedera: Magic Link email sign-ins and Hedera's fixed sub-cent fees ensure smooth rights record without wallet barriers. Market: TAM $2.4B — photography rights management | SAM $800M — professional photo licensing | SOM $35M — onchain rights records adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Instant Rights Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage photo licensing rights transparently onchain with no gas fees for creators. Discipline: Photography (rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins and Hedera's fixed sub-cent fees ensure smooth rights record without wallet barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Instant Rights Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gasless-metadata-tags-6 Title: Gasless Metadata Tags Theme: Photography (photography) · photo metadata Hedera hook: Magic Link email wallet [wallet UX] Pitch: Add and sync photo metadata onchain without gas fees or wallet complications. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless metadata updates for photographers. Market: TAM $2.4B — photo editing and metadata tools | SAM $450M — professional metadata management | SOM $12M — metadata tagging onchain users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Metadata Tags" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Add and sync photo metadata onchain without gas fees or wallet complications. Discipline: Photography (photo metadata). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless metadata updates for photographers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Metadata Tags" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-sponsored-tx-photo-sales-7 Title: Hedera's fixed sub-cent fees Photo Sales Theme: Photography (photography) · photo marketplace Hedera hook: Magic Link email wallet [wallet UX] Pitch: Sell your photos onchain with gasless transactions for buyers and sellers alike. Why Hedera: Hedera's fixed sub-cent fees lower barriers ensuring frictionless photo marketplace experience. Market: TAM $2.4B — photo stock and marketplace software | SAM $1B — pro photo marketplace revenue | SOM $40M — onchain marketplace early transactions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Hedera's fixed sub-cent fees Photo Sales" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sell your photos onchain with gasless transactions for buyers and sellers alike. Discipline: Photography (photo marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees lower barriers ensuring frictionless photo marketplace experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Hedera's fixed sub-cent fees Photo Sales" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-privy-nft-experiments-8 Title: the embedded wallet NFT Experiments Theme: Photography (photography) · photo NFT art Hedera hook: Magic Link email wallet [wallet UX] Pitch: Easily mint and share photo NFTs with zero-gas user experience via Magic Link email sign-ins. Why Hedera: Magic Link email sign-ins plus sponsor tx create frictionless NFT minting for photographers. Market: TAM $2.4B — photo NFT and art markets | SAM $600M — photo NFT minting tools | SOM $18M — gasless NFT minters among pros ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet NFT Experiments" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Easily mint and share photo NFTs with zero-gas user experience via Magic Link email sign-ins. Discipline: Photography (photo NFT art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus sponsor tx create frictionless NFT minting for photographers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet NFT Experiments" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gas-free-collaboration-9 Title: Gas-Free Collaboration Theme: Photography (photography) · team workflow Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collaborate on photo edits and shares onchain with zero gas fees for team members. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless multi-user workflows. Market: TAM $2.4B — pro photo collaboration tools | SAM $400M — photo team collaboration software | SOM $14M — gasless onchain team workflow users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gas-Free Collaboration" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaborate on photo edits and shares onchain with zero gas fees for team members. Discipline: Photography (team workflow). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enables seamless multi-user workflows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gas-Free Collaboration" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-event-albums-10 Title: Onchain Event Albums Theme: Photography (photography) · event photography Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and distribute event photo albums onchain with no gas fees for attendees. Why Hedera: Hedera's fixed sub-cent fees and Magic Link email sign-ins enable effortless event photo sharing at scale. Market: TAM $2.4B — event photo software market | SAM $350M — event album sharing solutions | SOM $9M — onchain event album users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Event Albums" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and distribute event photo albums onchain with no gas fees for attendees. Discipline: Photography (event photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees and Magic Link email sign-ins enable effortless event photo sharing at scale. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Event Albums" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-privy-photo-feedback-11 Title: the embedded wallet Photo Feedback Theme: Photography (photography) · photo critique Hedera hook: Magic Link email wallet [wallet UX] Pitch: Get instant, onchain photo feedback from peers with sponsored no-gas transactions. Why Hedera: Magic Link email sign-ins plus gasless tx unlock easy social critique. Market: TAM $2.4B — photo community and critique apps | SAM $200M — pro photo feedback platforms | SOM $6M — gasless onchain photo critique users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Photo Feedback" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Get instant, onchain photo feedback from peers with sponsored no-gas transactions. Discipline: Photography (photo critique). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus gasless tx unlock easy social critique. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Photo Feedback" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gasless-photo-auctions-12 Title: Gasless Photo Auctions Theme: Photography (photography) · photo auctions Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host and join photo auctions onchain with zero gas fees and simple wallet integration. Why Hedera: Hedera's fixed sub-cent fees plus Magic Link email sign-ins reduce friction in auction bids and sales. Market: TAM $2.4B — photo auction and sales software | SAM $300M — professional photo auction markets | SOM $10M — early gasless auction bidders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Photo Auctions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host and join photo auctions onchain with zero gas fees and simple wallet integration. Discipline: Photography (photo auctions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees plus Magic Link email sign-ins reduce friction in auction bids and sales. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Photo Auctions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-stamped-edits-13 Title: Chain-Stamped Edits Theme: Photography (photography) · edit provenance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Record photo edit history onchain instantly without needing users to pay gas. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless edit provenance tracking. Market: TAM $2.4B — photo editing software | SAM $450M — edit history and proof tools | SOM $13M — gasless onchain edit provenance users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Stamped Edits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record photo edit history onchain instantly without needing users to pay gas. Discipline: Photography (edit provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless edit provenance tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Stamped Edits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-sponsored-tx-model-releases-14 Title: Hedera's fixed sub-cent fees Model Releases Theme: Photography (photography) · legal docs Hedera hook: Magic Link email wallet [wallet UX] Pitch: Secure photo model releases onchain with gasless transactions for photographers and talent. Why Hedera: Magic Link email sign-ins plus gasless tx simplify legal document signing and storage. Market: TAM $2.4B — photography legal and compliance | SAM $150M — model release management | SOM $5M — onchain gasless legal doc users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Hedera's fixed sub-cent fees Model Releases" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure photo model releases onchain with gasless transactions for photographers and talent. Discipline: Photography (legal docs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus gasless tx simplify legal document signing and storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Hedera's fixed sub-cent fees Model Releases" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-photo-licensing-15 Title: Onchain Photo Licensing Theme: Photography (photography) · rights clearing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Negotiate and finalize photo licenses onchain with no gas fees for quick deals. Why Hedera: the embedded wallet Hedera's fixed sub-cent fees removes friction for fast onchain license agreements. Market: TAM $2.4B — photo license negotiation | SAM $700M — licensing transaction software | SOM $20M — onchain gasless license transactions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Photo Licensing" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Negotiate and finalize photo licenses onchain with no gas fees for quick deals. Discipline: Photography (rights clearing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Hedera's fixed sub-cent fees removes friction for fast onchain license agreements. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Photo Licensing" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gasless-watermark-tags-16 Title: Gasless Watermark Tags Theme: Photography (photography) · copyright protection Hedera hook: Magic Link email wallet [wallet UX] Pitch: Apply and verify photo watermarks onchain without wallet or gas hassles. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure smooth rights protection tagging. Market: TAM $2.4B — photo copyright tools | SAM $300M — watermarking and protection | SOM $8M — onchain watermark tagging users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Watermark Tags" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Apply and verify photo watermarks onchain without wallet or gas hassles. Discipline: Photography (copyright protection). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure smooth rights protection tagging. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Watermark Tags" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-privy-photo-assets-17 Title: the embedded wallet Photo Assets Theme: Photography (photography) · asset management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage photo assets onchain with no gas fees for easy organization and sharing. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplify asset governance. Market: TAM $2.4B — photography asset management | SAM $550M — pro asset management software | SOM $16M — gasless onchain asset managers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Photo Assets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage photo assets onchain with no gas fees for easy organization and sharing. Discipline: Photography (asset management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplify asset governance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Photo Assets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-sponsored-tx-photo-tips-18 Title: Hedera's fixed sub-cent fees Photo Tips Theme: Photography (photography) · content monetization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Receive tips for your photography onchain without users paying gas fees. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees deliver smooth, no-cost tipping experience. Market: TAM $2.4B — photo monetization platforms | SAM $250M — creator tip and support apps | SOM $7M — early gasless tip adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Hedera's fixed sub-cent fees Photo Tips" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Receive tips for your photography onchain without users paying gas fees. Discipline: Photography (content monetization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees deliver smooth, no-cost tipping experience. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Hedera's fixed sub-cent fees Photo Tips" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-verified-exif-19 Title: Chain-Verified EXIF Theme: Photography (photography) · photo metadata Hedera hook: Magic Link email wallet [wallet UX] Pitch: Verify and store immutable EXIF data onchain without wallet or gas barriers. Why Hedera: the embedded wallet’s Hedera's fixed sub-cent fees and wallets enable frictionless metadata proofing. Market: TAM $2.4B — photo metadata verification | SAM $400M — immutable photo data tools | SOM $11M — onchain EXIF verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Verified EXIF" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify and store immutable EXIF data onchain without wallet or gas barriers. Discipline: Photography (photo metadata). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s Hedera's fixed sub-cent fees and wallets enable frictionless metadata proofing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Verified EXIF" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-gasless-photo-badges-20 Title: Gasless Photo Badges Theme: Photography (photography) · community rewards Hedera hook: Magic Link email wallet [wallet UX] Pitch: Earn and showcase onchain badges for photography achievements with zero gas fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows effortless badge minting and display. Market: TAM $2.4B — photo community engagement | SAM $180M — rewards and badge systems | SOM $5M — gasless badge recipients ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Photo Badges" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Earn and showcase onchain badges for photography achievements with zero gas fees. Discipline: Photography (community rewards). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows effortless badge minting and display. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Photo Badges" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-onchain-lens-reviews-21 Title: Onchain Lens Reviews Theme: Photography (photography) · equipment reviews Hedera hook: Magic Link email wallet [wallet UX] Pitch: Submit and share authentic lens reviews onchain with no gas required from users. Why Hedera: Hedera's fixed sub-cent fees with Magic Link email sign-ins guarantees easy user contributions. Market: TAM $2.4B — photography gear review platforms | SAM $100M — pro lens review market | SOM $3M — onchain gasless review contributors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Onchain Lens Reviews" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Submit and share authentic lens reviews onchain with no gas required from users. Discipline: Photography (equipment reviews). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees with Magic Link email sign-ins guarantees easy user contributions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Onchain Lens Reviews" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-privy-photo-grants-22 Title: the embedded wallet Photo Grants Theme: Photography (photography) · creative funding Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute photography project grants onchain with Hedera's fixed sub-cent fees removing gas costs. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless fund transfer to creatives. Market: TAM $2.4B — photography funding and grants | SAM $150M — creative funding platforms | SOM $4M — gasless grant recipients ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Photo Grants" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute photography project grants onchain with Hedera's fixed sub-cent fees removing gas costs. Discipline: Photography (creative funding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless fund transfer to creatives. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Photo Grants" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-chain-linked-portfolios-23 Title: Chain-Linked Portfolios Theme: Photography (photography) · portfolio management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create gasless onchain portfolios linking proofs and metadata automatically. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees foster effortless portfolio updates. Market: TAM $2.4B — photography portfolio software | SAM $500M — professional portfolio tools | SOM $14M — gasless onchain portfolio users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Linked Portfolios" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create gasless onchain portfolios linking proofs and metadata automatically. Discipline: Photography (portfolio management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees foster effortless portfolio updates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Linked Portfolios" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-sponsored-tx-photo-collages-24 Title: Hedera's fixed sub-cent fees Photo Collages Theme: Photography (photography) · creative assembly Hedera hook: Magic Link email wallet [wallet UX] Pitch: Build and share photo collages onchain instantly without users paying gas fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable smooth collage minting. Market: TAM $2.4B — creative photo editing software | SAM $350M — collage and montage tools | SOM $9M — gasless photo collage creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Hedera's fixed sub-cent fees Photo Collages" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Build and share photo collages onchain instantly without users paying gas fees. Discipline: Photography (creative assembly). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable smooth collage minting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Hedera's fixed sub-cent fees Photo Collages" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-trueshot-ledger-0 Title: TrueShot Ledger Theme: Photography (photography) · photo authenticity Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Prove original photo ownership to combat unauthorized reuse and forgery. Why Hedera: NFT provenance mint creates immutable proof of original photo creation linked to IPFS. Market: TAM $2.4B — global photo software market | SAM $500M — photo editing and verification tools | SOM $50M — forensic authenticity services for professional photographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TrueShot Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Prove original photo ownership to combat unauthorized reuse and forgery. Discipline: Photography (photo authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mint creates immutable proof of original photo creation linked to IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TrueShot Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-edittrace-chain-1 Title: EditTrace Chain Theme: Photography (photography) · photo editing history Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track complete edit histories on-chain for transparent creative workflows. Why Hedera: HTS NFT tokens record each edit step linked to immutable IPFS hashes. Market: TAM $2.4B — global photo software industry | SAM $300M — professional photo editing software | SOM $30M — photojournalism workflow tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EditTrace Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track complete edit histories on-chain for transparent creative workflows. Discipline: Photography (photo editing history). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens record each edit step linked to immutable IPFS hashes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EditTrace Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-proprint-certify-2 Title: ProPrint Certify Theme: Photography (photography) · fine art prints Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Certify fine art photo prints with tamper-proof digital provenance. Why Hedera: NFT minting on Hedera testnet links digital print files and ownership records securely. Market: TAM $2.4B — photo software & art market | SAM $400M — fine art photography prints | SOM $40M — certified print marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ProPrint Certify" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Certify fine art photo prints with tamper-proof digital provenance. Discipline: Photography (fine art prints). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting on Hedera testnet links digital print files and ownership records securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ProPrint Certify" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-snapevent-auth-3 Title: SnapEvent Auth Theme: Photography (photography) · event photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Validate event photo ownership instantly with blockchain timestamps. Why Hedera: Timestamped NFT minting ensures event photo origin proof tied to IPFS. Market: TAM $2.4B — photo software global market | SAM $350M — event photo management software | SOM $35M — event photography authentication services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SnapEvent Auth" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Validate event photo ownership instantly with blockchain timestamps. Discipline: Photography (event photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Timestamped NFT minting ensures event photo origin proof tied to IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SnapEvent Auth" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-newsframe-provenance-4 Title: NewsFrame Provenance Theme: Photography (photography) · photojournalism Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate frontline news photos with immutable creator-owned tokens. Why Hedera: HTS NFT on Hedera testnet guarantees verified news photo provenance and ownership. Market: TAM $2.4B — photo software and media tools | SAM $250M — photojournalism tools | SOM $25M — newsroom photo verification systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NewsFrame Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate frontline news photos with immutable creator-owned tokens. Discipline: Photography (photojournalism). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT on Hedera testnet guarantees verified news photo provenance and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NewsFrame Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-stocksnap-rights-5 Title: StockSnap Rights Theme: Photography (photography) · stock photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure and track photo stock licensing with NFT-based ownership proof. Why Hedera: Onchain tokens tie licenses and usage rights to IPFS-hosted photos. Market: TAM $2.4B — photo software and licensing | SAM $600M — stock photo platforms | SOM $60M — licensing verification solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StockSnap Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure and track photo stock licensing with NFT-based ownership proof. Discipline: Photography (stock photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokens tie licenses and usage rights to IPFS-hosted photos. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StockSnap Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-lensstory-archive-6 Title: LensStory Archive Theme: Photography (photography) · photography archives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create permanent, traceable archives of photo collections on blockchain. Why Hedera: HTS NFT minting records collection provenance linked to IPFS metadata. Market: TAM $2.4B — photo software and archiving | SAM $200M — professional photo archives | SOM $20M — archival provenance tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LensStory Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create permanent, traceable archives of photo collections on blockchain. Discipline: Photography (photography archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting records collection provenance linked to IPFS metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LensStory Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-collaboshot-chain-7 Title: CollaboShot Chain Theme: Photography (photography) · collaborative shoots Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely track multi-photographer project ownership with shared NFTs. Why Hedera: HTS NFT tokens authenticate multi-creator ownership on immutable IPFS CIDs. Market: TAM $2.4B — photo software market | SAM $150M — collaborative photo projects | SOM $15M — shared ownership verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CollaboShot Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely track multi-photographer project ownership with shared NFTs. Discipline: Photography (collaborative shoots). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens authenticate multi-creator ownership on immutable IPFS CIDs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CollaboShot Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-vintagevibe-mint-8 Title: VintageVibe Mint Theme: Photography (photography) · vintage photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Digitally mint vintage photo scans to preserve original source provenance. Why Hedera: NFTs link vintage photo IPFS assets to creator-defined provenance records. Market: TAM $2.4B — photo software and digitization | SAM $100M — vintage photography market | SOM $10M — provenance tools for vintage photographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VintageVibe Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitally mint vintage photo scans to preserve original source provenance. Discipline: Photography (vintage photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs link vintage photo IPFS assets to creator-defined provenance records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VintageVibe Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-framechain-gallery-9 Title: FrameChain Gallery Theme: Photography (photography) · digital galleries Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create blockchain-backed digital photo galleries with verified provenance. Why Hedera: HTS NFT tokens mint and prove ownership of gallery collections on IPFS. Market: TAM $2.4B — photo software and galleries | SAM $180M — digital art and photo galleries | SOM $18M — blockchain gallery software ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameChain Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create blockchain-backed digital photo galleries with verified provenance. Discipline: Photography (digital galleries). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens mint and prove ownership of gallery collections on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameChain Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-focusproof-vault-10 Title: FocusProof Vault Theme: Photography (photography) · photo legal evidence Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Use on-chain minted NFTs as legally admissible photo evidence. Why Hedera: Immutable NFT ownership and IPFS content hashes validate photo authenticity. Market: TAM $2.4B — photo software and legal tech | SAM $120M — forensic photo software | SOM $12M — legal evidence verification services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FocusProof Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use on-chain minted NFTs as legally admissible photo evidence. Discipline: Photography (photo legal evidence). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFT ownership and IPFS content hashes validate photo authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FocusProof Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-macromint-origins-11 Title: MacroMint Origins Theme: Photography (photography) · macro photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate original macro shots with creator-owned NFTs. Why Hedera: HTS NFT ensures unique, tamper-proof records linked to IPFS macro images. Market: TAM $2.4B — photography software market | SAM $90M — macro photography niche | SOM $9M — provenance tools for macro artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MacroMint Origins" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate original macro shots with creator-owned NFTs. Discipline: Photography (macro photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT ensures unique, tamper-proof records linked to IPFS macro images. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MacroMint Origins" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-timelapse-token-12 Title: TimeLapse Token Theme: Photography (photography) · time-lapse photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Record and prove time-lapse photo sequences provenance on blockchain. Why Hedera: NFTs link IPFS stored time-lapse sequences with immutable ownership proof. Market: TAM $2.4B — photo software industry | SAM $80M — time-lapse production tools | SOM $8M — provenance for time-lapse creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TimeLapse Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and prove time-lapse photo sequences provenance on blockchain. Discipline: Photography (time-lapse photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs link IPFS stored time-lapse sequences with immutable ownership proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TimeLapse Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-portraitclaim-chain-13 Title: PortraitClaim Chain Theme: Photography (photography) · portrait photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure portrait ownership and licensing with NFT provenance minting. Why Hedera: HTS NFT tokens validate individual portrait copyright tied to IPFS images. Market: TAM $2.4B — photo software and licensing | SAM $400M — portrait photography market | SOM $40M — portrait rights management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PortraitClaim Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure portrait ownership and licensing with NFT provenance minting. Discipline: Photography (portrait photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens validate individual portrait copyright tied to IPFS images. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PortraitClaim Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-droneshot-ledger-14 Title: DroneShot Ledger Theme: Photography (photography) · drone photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track drone photo creations with immutable blockchain provenance. Why Hedera: NFT minting provides secure, traceable drone imagery ownership proof. Market: TAM $2.4B — photo software and aerial imaging | SAM $150M — drone photo market | SOM $15M — drone photo provenance tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DroneShot Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track drone photo creations with immutable blockchain provenance. Discipline: Photography (drone photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting provides secure, traceable drone imagery ownership proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DroneShot Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-filtermint-chain-15 Title: FilterMint Chain Theme: Photography (photography) · creative filters Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Prove filter creation and usage history on-chain for unique photo effects. Why Hedera: HTS NFT records filter ownership linked to IPFS-hosted effect metadata. Market: TAM $2.4B — photo editing software market | SAM $100M — filter and effect marketplaces | SOM $10M — provenance tracking for filter creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FilterMint Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Prove filter creation and usage history on-chain for unique photo effects. Discipline: Photography (creative filters). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT records filter ownership linked to IPFS-hosted effect metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FilterMint Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-photostory-token-16 Title: PhotoStory Token Theme: Photography (photography) · photo narratives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Attach narrative stories securely to photos using NFT provenance. Why Hedera: Minting NFTs with IPFS metadata links photos to verified creator stories. Market: TAM $2.4B — photo software and storytelling | SAM $90M — visual storytelling tools | SOM $9M — provenance tools for photo narratives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PhotoStory Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach narrative stories securely to photos using NFT provenance. Discipline: Photography (photo narratives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Minting NFTs with IPFS metadata links photos to verified creator stories. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PhotoStory Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-360proof-chain-17 Title: 360Proof Chain Theme: Photography (photography) · 360° photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate and secure ownership of 360-degree photos on the blockchain. Why Hedera: HTS NFT tokens ensure tamper-proof provenance for 360° IPFS photo assets. Market: TAM $2.4B — photo software market | SAM $75M — 360° photo production | SOM $7.5M — provenance for immersive images ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "360Proof Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate and secure ownership of 360-degree photos on the blockchain. Discipline: Photography (360° photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens ensure tamper-proof provenance for 360° IPFS photo assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "360Proof Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-eventsnap-token-18 Title: EventSnap Token Theme: Photography (photography) · wedding photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Guarantee wedding photo originality and ownership with NFT provenance. Why Hedera: NFT minting records each wedding photo’s creator and IPFS content hash. Market: TAM $2.4B — photo software and event markets | SAM $300M — wedding photography market | SOM $30M — blockchain provenance services for couples ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EventSnap Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Guarantee wedding photo originality and ownership with NFT provenance. Discipline: Photography (wedding photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting records each wedding photo’s creator and IPFS content hash. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EventSnap Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-colorgrade-chain-19 Title: ColorGrade Chain Theme: Photography (photography) · color grading Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track and prove original photo color grades and presets on-chain. Why Hedera: HTS NFT mints unique color grade profiles linked to IPFS storage. Market: TAM $2.4B — photo software and editing | SAM $120M — color grading tools | SOM $12M — provenance for colorists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorGrade Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and prove original photo color grades and presets on-chain. Discipline: Photography (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT mints unique color grade profiles linked to IPFS storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorGrade Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-wildlifemint-ledger-20 Title: WildlifeMint Ledger Theme: Photography (photography) · wildlife photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate unique wildlife photos with immutable NFT provenance tokens. Why Hedera: HTS NFT records verified ownership linked to IPFS wildlife imagery. Market: TAM $2.4B — photo software and nature markets | SAM $130M — wildlife photo market | SOM $13M — provenance tracking for wildlife photographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "WildlifeMint Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate unique wildlife photos with immutable NFT provenance tokens. Discipline: Photography (wildlife photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT records verified ownership linked to IPFS wildlife imagery. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "WildlifeMint Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-macrofocus-token-21 Title: MacroFocus Token Theme: Photography (photography) · product photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and verify product photo assets via on-chain provenance. Why Hedera: NFTs linked to IPFS ensure trusted ownership and royalty tracking. Market: TAM $2.4B — photo software and commerce | SAM $180M — product photography tools | SOM $18M — provenance for commercial photo studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MacroFocus Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and verify product photo assets via on-chain provenance. Discipline: Photography (product photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs linked to IPFS ensure trusted ownership and royalty tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MacroFocus Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-photoset-chain-22 Title: PhotoSet Chain Theme: Photography (photography) · photo series Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Prove ownership and authenticity of curated photo series collections. Why Hedera: HTS NFT tokens mint entire series linked immutably to IPFS galleries. Market: TAM $2.4B — photo software and collections | SAM $110M — series and portfolio platforms | SOM $11M — provenance tools for photographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PhotoSet Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Prove ownership and authenticity of curated photo series collections. Discipline: Photography (photo series). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens mint entire series linked immutably to IPFS galleries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PhotoSet Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-urbanview-token-23 Title: UrbanView Token Theme: Photography (photography) · urban photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate urban scene photos for creators and licensing platforms. Why Hedera: HTS NFT minting verifies unique IPFS urban photo ownership records. Market: TAM $2.4B — photo software and media | SAM $95M — urban photography market | SOM $9.5M — blockchain provenance for city photos ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "UrbanView Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate urban scene photos for creators and licensing platforms. Discipline: Photography (urban photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting verifies unique IPFS urban photo ownership records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "UrbanView Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA photography-nightshot-mint-24 Title: NightShot Mint Theme: Photography (photography) · night photography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure and verify ownership of night photos with NFT provenance. Why Hedera: HTS NFT tokens create immutable proofs tied to IPFS night photo data. Market: TAM $2.4B — photo software worldwide | SAM $70M — night photography niche | SOM $7M — provenance services for night photographers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NightShot Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure and verify ownership of night photos with NFT provenance. Discipline: Photography (night photography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens create immutable proofs tied to IPFS night photo data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NightShot Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Theater & Live Performance directors, playwrights, performers, lighting and stage designers ============================================================================== ------------------------------------------------------------------------------ IDEA theater-scriptchain-ledger-0 Title: ScriptChain Ledger Theme: Theater & Live Performance (theater) · playwright collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely track and timestamp script drafts through an immutable blockchain ledger. Why Hedera: Hedera testnet smart contract ensures tamper-proof script version control and authorship verification. Market: TAM $1B — global digital scriptwriting tools market | SAM $200M — playwright collaboration software segment | SOM $50M — early-adopting theater companies using blockchain for IP protection ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptChain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely track and timestamp script drafts through an immutable blockchain ledger. Discipline: Theater & Live Performance (playwright collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contract ensures tamper-proof script version control and authorship verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptChain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-roleauction-platform-1 Title: RoleAuction Platform Theme: Theater & Live Performance (theater) · casting marketplace Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralized audition bids let performers compete transparently for roles via smart contracts. Why Hedera: Hedera testnet contracts automate bidding and escrow without intermediaries, enhancing trust in casting. Market: TAM $3B — global casting agency market | SAM $500M — online casting platforms | SOM $100M — theater companies adopting blockchain casting tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RoleAuction Platform" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralized audition bids let performers compete transparently for roles via smart contracts. Discipline: Theater & Live Performance (casting marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automate bidding and escrow without intermediaries, enhancing trust in casting. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RoleAuction Platform" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stagelight-token-2 Title: StageLight Token Theme: Theater & Live Performance (theater) · lighting rights management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue and trade usage rights for lighting designs using onchain tokens to protect creators. Why Hedera: Hedera testnet smart contracts enable verifiable licensing and royalty distribution for lighting designers. Market: TAM $2B — theater lighting equipment market | SAM $400M — lighting design intellectual property segment | SOM $80M — digital rights management adoption in live performance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StageLight Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue and trade usage rights for lighting designs using onchain tokens to protect creators. Discipline: Theater & Live Performance (lighting rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable verifiable licensing and royalty distribution for lighting designers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StageLight Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-promptchain-impro-3 Title: PromptChain Impro Theme: Theater & Live Performance (theater) · improvisation prompts Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Generate and track custom improv prompts securely stored and shared via smart contracts. Why Hedera: Hedera testnet platform ensures provenance and fair sharing of creative prompts in improv communities. Market: TAM $500M — improv theater training market | SAM $100M — improv prompt tools and apps | SOM $20M — improv troupes experimenting with blockchain prompts ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PromptChain Impro" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Generate and track custom improv prompts securely stored and shared via smart contracts. Discipline: Theater & Live Performance (improvisation prompts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet platform ensures provenance and fair sharing of creative prompts in improv communities. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PromptChain Impro" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-tickettrust-nft-4 Title: TicketTrust NFT Theme: Theater & Live Performance (theater) · ticket authenticity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Use blockchain to mint tamper-proof NFT tickets preventing counterfeiting for live performances. Why Hedera: Hedera testnet contracts provide secure, verifiable ownership records for live event tickets. Market: TAM $15B — global ticketing market | SAM $3B — digital ticketing platforms | SOM $600M — blockchain ticket adoption in theaters and venues ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TicketTrust NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use blockchain to mint tamper-proof NFT tickets preventing counterfeiting for live performances. Discipline: Theater & Live Performance (ticket authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide secure, verifiable ownership records for live event tickets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TicketTrust NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-propchain-registry-5 Title: PropChain Registry Theme: Theater & Live Performance (theater) · prop ownership Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record and transfer ownership history of stage props transparently on a blockchain registry. Why Hedera: Hedera testnet smart contract provides immutable tracking for valuable or rented props. Market: TAM $1B — theatrical prop rental and sales market | SAM $200M — prop inventory management systems | SOM $40M — prop companies using blockchain asset tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropChain Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and transfer ownership history of stage props transparently on a blockchain registry. Discipline: Theater & Live Performance (prop ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contract provides immutable tracking for valuable or rented props. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropChain Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scriptroyalty-split-6 Title: ScriptRoyalty Split Theme: Theater & Live Performance (theater) · royalty distribution Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate fair payouts to playwrights, directors, and actors using onchain royalty splits. Why Hedera: Hedera testnet smart contracts ensure transparent, trustless revenue sharing from live performances. Market: TAM $10B — global live performance revenue | SAM $2B — royalty management software market | SOM $400M — theater companies seeking automated royalty solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptRoyalty Split" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate fair payouts to playwrights, directors, and actors using onchain royalty splits. Discipline: Theater & Live Performance (royalty distribution). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure transparent, trustless revenue sharing from live performances. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptRoyalty Split" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stagecrew-dao-7 Title: StageCrew DAO Theme: Theater & Live Performance (theater) · crew coordination Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralize stage crew decisions and resource allocation through a transparent DAO governed by smart contracts. Why Hedera: Hedera testnet enables trustless voting and fund distribution among live production teams. Market: TAM $500M — stage crew management software | SAM $100M — decentralized team coordination tools | SOM $25M — theater companies piloting DAO-based crew workflows ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StageCrew DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize stage crew decisions and resource allocation through a transparent DAO governed by smart contracts. Discipline: Theater & Live Performance (crew coordination). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet enables trustless voting and fund distribution among live production teams. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StageCrew DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playmint-nft-8 Title: PlayMint NFT Theme: Theater & Live Performance (theater) · script NFTs Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint unique digital editions of scripts as NFTs to protect intellectual property and track sales. Why Hedera: Hedera testnet contracts provide verifiable ownership and provenance for playwrights’ works. Market: TAM $800M — digital publishing for theater | SAM $150M — NFT market for creative scripts | SOM $30M — theatrical NFT experiments in IP protection ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlayMint NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique digital editions of scripts as NFTs to protect intellectual property and track sales. Discipline: Theater & Live Performance (script NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide verifiable ownership and provenance for playwrights’ works. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlayMint NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-actorstake-platform-9 Title: ActorStake Platform Theme: Theater & Live Performance (theater) · performance staking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Allow fans to stake tokens on actors’ performances, rewarding success with blockchain incentives. Why Hedera: Hedera testnet smart contracts manage stakes and payouts transparently for audience engagement. Market: TAM $2B — live performance fan engagement market | SAM $400M — blockchain fan staking platforms | SOM $80M — theaters exploring tokenized fan incentives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ActorStake Platform" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Allow fans to stake tokens on actors’ performances, rewarding success with blockchain incentives. Discipline: Theater & Live Performance (performance staking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts manage stakes and payouts transparently for audience engagement. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ActorStake Platform" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightcue-automation-10 Title: LightCue Automation Theme: Theater & Live Performance (theater) · lighting cue logs Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record and trigger lighting cues onchain to verify show timing and enable automated playback. Why Hedera: Hedera testnet contracts guarantee immutable cue sequences and decentralize control of lighting shows. Market: TAM $1.5B — live lighting control market | SAM $300M — automated lighting cue systems | SOM $60M — theaters integrating blockchain control for lights ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightCue Automation" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and trigger lighting cues onchain to verify show timing and enable automated playback. Discipline: Theater & Live Performance (lighting cue logs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts guarantee immutable cue sequences and decentralize control of lighting shows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightCue Automation" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scenechange-nfts-11 Title: SceneChange NFTs Theme: Theater & Live Performance (theater) · scene ownership Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Artists mint NFT certificates of original scene designs for resale and licensing. Why Hedera: Hedera testnet smart contracts prove scene design ownership and facilitate secondary market sales. Market: TAM $700M — stage design market | SAM $150M — digital licensing of scene concepts | SOM $35M — scene designers trialing blockchain rights ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneChange NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Artists mint NFT certificates of original scene designs for resale and licensing. Discipline: Theater & Live Performance (scene ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts prove scene design ownership and facilitate secondary market sales. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneChange NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playbill-ledger-12 Title: Playbill Ledger Theme: Theater & Live Performance (theater) · production documentation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely record production credits and changes transparently on a public ledger. Why Hedera: Hedera testnet contracts enable immutable, timestamped records of live performance production versions. Market: TAM $400M — playbill and program publishing | SAM $80M — production documentation services | SOM $15M — theaters innovating version control with blockchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playbill Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely record production credits and changes transparently on a public ledger. Discipline: Theater & Live Performance (production documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable immutable, timestamped records of live performance production versions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playbill Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-audiencevote-dao-13 Title: AudienceVote DAO Theme: Theater & Live Performance (theater) · live feedback Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Collect and tally audience votes on performances in a transparent, tamper-proof DAO system. Why Hedera: Hedera testnet smart contract ensures verified, unbiased audience participation results instantly. Market: TAM $1B — audience engagement tools | SAM $200M — live voting and polling platforms | SOM $40M — theaters adopting blockchain audience feedback ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AudienceVote DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collect and tally audience votes on performances in a transparent, tamper-proof DAO system. Discipline: Theater & Live Performance (live feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contract ensures verified, unbiased audience participation results instantly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AudienceVote DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-backstage-token-14 Title: Backstage Token Theme: Theater & Live Performance (theater) · access control Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue tokens granting secure, onchain backstage access for staff and VIPs. Why Hedera: Hedera testnet contracts provide verifiable, transferable digital credentials for event access control. Market: TAM $800M — access management market | SAM $160M — digital credentialing in live events | SOM $32M — theaters piloting token-based backstage passes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Backstage Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue tokens granting secure, onchain backstage access for staff and VIPs. Discipline: Theater & Live Performance (access control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide verifiable, transferable digital credentials for event access control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Backstage Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-rehearsallog-chain-15 Title: RehearsalLog Chain Theme: Theater & Live Performance (theater) · rehearsal tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Immutably store rehearsal attendance and notes to improve production accountability. Why Hedera: Hedera testnet smart contracts lock in rehearsal data to prevent disputes over participation. Market: TAM $300M — rehearsal management tools | SAM $60M — production workflow tracking software | SOM $10M — blockchain adoption for rehearsal logs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RehearsalLog Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Immutably store rehearsal attendance and notes to improve production accountability. Discipline: Theater & Live Performance (rehearsal tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts lock in rehearsal data to prevent disputes over participation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RehearsalLog Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scriptlens-dao-16 Title: ScriptLens DAO Theme: Theater & Live Performance (theater) · script co-creation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable playwrights and directors to co-own scripts via decentralized governance and revenue sharing. Why Hedera: Hedera testnet contracts enable onchain voting and rights management between collaborators. Market: TAM $1B — collaborative writing software | SAM $200M — decentralized creative platforms | SOM $40M — theater groups experimenting with co-creation DAOs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptLens DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable playwrights and directors to co-own scripts via decentralized governance and revenue sharing. Discipline: Theater & Live Performance (script co-creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable onchain voting and rights management between collaborators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptLens DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-sceneswap-marketplace-17 Title: SceneSwap Marketplace Theme: Theater & Live Performance (theater) · design exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Trade and license scene and set designs through a verified onchain marketplace. Why Hedera: Hedera testnet smart contracts securely facilitate ownership transfer and licensing payments. Market: TAM $1.2B — set design and rental market | SAM $250M — digital creative marketplaces | SOM $50M — early adopters in blockchain design exchange ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneSwap Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade and license scene and set designs through a verified onchain marketplace. Discipline: Theater & Live Performance (design exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts securely facilitate ownership transfer and licensing payments. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneSwap Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-performerbadge-nft-18 Title: PerformerBadge NFT Theme: Theater & Live Performance (theater) · credentials verification Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint verifiable NFT badges certifying performer skills and achievements. Why Hedera: Hedera testnet smart contracts provide immutable proof of credentials within the live performance industry. Market: TAM $700M — talent credentialing market | SAM $140M — digital certification platforms | SOM $28M — talent agencies piloting blockchain verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PerformerBadge NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint verifiable NFT badges certifying performer skills and achievements. Discipline: Theater & Live Performance (credentials verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide immutable proof of credentials within the live performance industry. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PerformerBadge NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightfx-tokenize-19 Title: LightFX Tokenize Theme: Theater & Live Performance (theater) · special effects licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Tokenize and license stage special effects designs to productions worldwide securely. Why Hedera: Hedera testnet contracts enable transparent rights management for special effects creators. Market: TAM $900M — live performance special effects market | SAM $180M — digital licensing of effects designs | SOM $36M — special effects designers adopting blockchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightFX Tokenize" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize and license stage special effects designs to productions worldwide securely. Discipline: Theater & Live Performance (special effects licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable transparent rights management for special effects creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightFX Tokenize" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-caststake-dao-20 Title: CastStake DAO Theme: Theater & Live Performance (theater) · fundraising Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralize theater production funding by letting stakeholders vote and invest through DAO tokens. Why Hedera: Hedera testnet smart contracts ensure transparent disbursement and stakeholder governance. Market: TAM $5B — theater production financing market | SAM $1B — crowdfunding platforms for arts | SOM $200M — blockchain-powered arts fundraising pilots ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CastStake DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize theater production funding by letting stakeholders vote and invest through DAO tokens. Discipline: Theater & Live Performance (fundraising). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure transparent disbursement and stakeholder governance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CastStake DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-propstoken-swap-21 Title: PropsToken Swap Theme: Theater & Live Performance (theater) · collaborative prop lending Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create a tokenized system for lending and borrowing props between theater companies. Why Hedera: Hedera testnet smart contracts automate lending agreements and track ownership transfers securely. Market: TAM $1B — theatrical prop rental market | SAM $200M — shared asset management platforms | SOM $40M — prop rental companies using blockchain tokens ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropsToken Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a tokenized system for lending and borrowing props between theater companies. Discipline: Theater & Live Performance (collaborative prop lending). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts automate lending agreements and track ownership transfers securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropsToken Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stagesound-dao-22 Title: StageSound DAO Theme: Theater & Live Performance (theater) · sound design collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Coordinate and monetize collective sound design projects with transparent DAOs and revenue splits. Why Hedera: Hedera testnet contracts enable trustless profit sharing and collaborative decision-making. Market: TAM $1.2B — live sound design market | SAM $250M — collaborative music and sound platforms | SOM $50M — sound designers testing blockchain collaboration ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StageSound DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Coordinate and monetize collective sound design projects with transparent DAOs and revenue splits. Discipline: Theater & Live Performance (sound design collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable trustless profit sharing and collaborative decision-making. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StageSound DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-encore-reward-23 Title: Encore Reward Theme: Theater & Live Performance (theater) · audience loyalty Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Tokenize audience rewards to incentivize repeat attendance and engagement at live performances. Why Hedera: Hedera testnet smart contracts manage transparent loyalty point issuance and redemption. Market: TAM $3B — audience loyalty programs in live events | SAM $600M — blockchain loyalty reward platforms | SOM $120M — theaters integrating token-based rewards ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Encore Reward" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize audience rewards to incentivize repeat attendance and engagement at live performances. Discipline: Theater & Live Performance (audience loyalty). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts manage transparent loyalty point issuance and redemption. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Encore Reward" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-setpiece-provenance-24 Title: SetPiece Provenance Theme: Theater & Live Performance (theater) · historical archival Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Archive and authenticate historical set pieces’ origin and usage on an immutable blockchain record. Why Hedera: Hedera testnet contracts provide trusted provenance for valuable theatrical artifacts. Market: TAM $400M — theatrical artifact market | SAM $80M — digital archiving solutions | SOM $16M — museums and theaters adopting blockchain provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SetPiece Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive and authenticate historical set pieces’ origin and usage on an immutable blockchain record. Discipline: Theater & Live Performance (historical archival). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide trusted provenance for valuable theatrical artifacts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SetPiece Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stagelight-archive-0 Title: StageLight Archive Theme: Theater & Live Performance (theater) · lighting design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and access dynamic stage lighting setups with permanent IPFS storage for reliable reuse and sharing. Why Hedera: Pinata's IPFS ensures lighting designs remain immutable and globally accessible for production teams. Market: TAM $3B — global theatrical lighting market | SAM $500M — digital lighting design software | SOM $50M — niche stage lighting archival tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StageLight Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and access dynamic stage lighting setups with permanent IPFS storage for reliable reuse and sharing. Discipline: Theater & Live Performance (lighting design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's IPFS ensures lighting designs remain immutable and globally accessible for production teams. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StageLight Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playscript-vault-1 Title: PlayScript Vault Theme: Theater & Live Performance (theater) · playwriting Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely save, share, and version play scripts on IPFS to preserve authorship and facilitate collaboration. Why Hedera: Pinata JWT upload guarantees immutable script versions pinned with unique CIDs for provenance. Market: TAM $4B — global playwriting and publishing | SAM $700M — digital script collaboration tools | SOM $70M — playwright-focused IPFS script repositories ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlayScript Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely save, share, and version play scripts on IPFS to preserve authorship and facilitate collaboration. Discipline: Theater & Live Performance (playwriting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT upload guarantees immutable script versions pinned with unique CIDs for provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlayScript Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-costumemood-board-2 Title: CostumeMood Board Theme: Theater & Live Performance (theater) · costume design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and share costume sketches and mood boards on IPFS for collaborative costume creation. Why Hedera: Pinata enables persistent, tamper-proof storage of costume design images and metadata. Market: TAM $2B — theatrical costume industry | SAM $400M — digital costume design platforms | SOM $30M — costume-specific IPFS mood board tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CostumeMood Board" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and share costume sketches and mood boards on IPFS for collaborative costume creation. Discipline: Theater & Live Performance (costume design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables persistent, tamper-proof storage of costume design images and metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CostumeMood Board" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-actorportfolio-hub-3 Title: ActorPortfolio Hub Theme: Theater & Live Performance (theater) · performance showcase Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Actors upload and pin portfolios including headshots and performance clips permanently on IPFS for casting accessibility. Why Hedera: IPFS via Pinata ensures persistent availability and integrity of actor showcase content. Market: TAM $3.5B — global talent management | SAM $600M — digital actor portfolios | SOM $40M — IPFS-based actor profile services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ActorPortfolio Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Actors upload and pin portfolios including headshots and performance clips permanently on IPFS for casting accessibility. Discipline: Theater & Live Performance (performance showcase). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata ensures persistent availability and integrity of actor showcase content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ActorPortfolio Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-setdesign-manifest-4 Title: SetDesign Manifest Theme: Theater & Live Performance (theater) · set design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and share complex 3D set designs and manifests immutably on IPFS for production coordination. Why Hedera: Pinata's JWT upload pins large JSON manifests reliably to IPFS for decentralized access. Market: TAM $2.5B — stage set production | SAM $450M — digital set design software | SOM $35M — set design IPFS manifest platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SetDesign Manifest" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and share complex 3D set designs and manifests immutably on IPFS for production coordination. Discipline: Theater & Live Performance (set design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's JWT upload pins large JSON manifests reliably to IPFS for decentralized access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SetDesign Manifest" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightingcues-ledger-5 Title: LightingCues Ledger Theme: Theater & Live Performance (theater) · lighting cues Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Save and distribute lighting cue sequences as pinned JSON on IPFS for synchronized performances. Why Hedera: Pinata allows secure, permanent pinning of cue sequences accessible to all crew members. Market: TAM $1.5B — theatrical lighting management | SAM $300M — digital cueing tools | SOM $25M — IPFS-based lighting cue management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightingCues Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save and distribute lighting cue sequences as pinned JSON on IPFS for synchronized performances. Discipline: Theater & Live Performance (lighting cues). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata allows secure, permanent pinning of cue sequences accessible to all crew members. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightingCues Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playpromo-kit-6 Title: PlayPromo Kit Theme: Theater & Live Performance (theater) · marketing collateral Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin play promo images and metadata on IPFS to ensure immutable, globally accessible marketing assets. Why Hedera: Pinata provides permanent CID links ensuring marketing materials remain hosted reliably. Market: TAM $4B — live performance marketing | SAM $800M — digital promotion platforms | SOM $50M — IPFS-backed theatrical promo kits ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlayPromo Kit" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin play promo images and metadata on IPFS to ensure immutable, globally accessible marketing assets. Discipline: Theater & Live Performance (marketing collateral). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata provides permanent CID links ensuring marketing materials remain hosted reliably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlayPromo Kit" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scriptrevision-chain-7 Title: ScriptRevision Chain Theme: Theater & Live Performance (theater) · script editing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Immutable version control for script edits stored and shared pinned on IPFS for playwrights and directors. Why Hedera: Pinata's JWT upload guarantees tamper-proof, timestamped script versions accessible globally. Market: TAM $4B — script development market | SAM $600M — digital script editing | SOM $45M — IPFS-based script version tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptRevision Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Immutable version control for script edits stored and shared pinned on IPFS for playwrights and directors. Discipline: Theater & Live Performance (script editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's JWT upload guarantees tamper-proof, timestamped script versions accessible globally. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptRevision Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-setprops-catalog-8 Title: SetProps Catalog Theme: Theater & Live Performance (theater) · props management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and index prop images and descriptions on IPFS for collaborative rental and inventory management. Why Hedera: Pinata's IPFS pinning offers reliable permalinks for decentralized prop catalogs. Market: TAM $1.8B — theatrical prop rental | SAM $350M — digital prop databases | SOM $20M — IPFS prop catalog solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SetProps Catalog" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and index prop images and descriptions on IPFS for collaborative rental and inventory management. Discipline: Theater & Live Performance (props management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's IPFS pinning offers reliable permalinks for decentralized prop catalogs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SetProps Catalog" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-soundscape-archive-9 Title: Soundscape Archive Theme: Theater & Live Performance (theater) · sound design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin ambient and effect sound files with metadata on IPFS to ensure lasting availability and sharing. Why Hedera: Pinata supports pinning large audio files and metadata immutably on IPFS for sound designers. Market: TAM $2B — live sound design | SAM $400M — digital sound libraries | SOM $30M — IPFS-based sound archival tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Soundscape Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin ambient and effect sound files with metadata on IPFS to ensure lasting availability and sharing. Discipline: Theater & Live Performance (sound design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata supports pinning large audio files and metadata immutably on IPFS for sound designers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Soundscape Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-performancereview-ledger-10 Title: PerformanceReview Ledger Theme: Theater & Live Performance (theater) · audience feedback Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store aggregated audience reviews as pinned JSON on IPFS guaranteeing unalterable feedback records. Why Hedera: Pinata pins feedback data securely, ensuring transparency and trust in review storage. Market: TAM $3B — live performance feedback | SAM $500M — digital review platforms | SOM $25M — IPFS audience review services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PerformanceReview Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store aggregated audience reviews as pinned JSON on IPFS guaranteeing unalterable feedback records. Discipline: Theater & Live Performance (audience feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins feedback data securely, ensuring transparency and trust in review storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PerformanceReview Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightingpalette-exchange-11 Title: LightingPalette Exchange Theme: Theater & Live Performance (theater) · color study Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and trade curated lighting color palettes as images and data on IPFS for designers to reuse. Why Hedera: Pinata’s JWT upload ensures palettes are permanently stored and shareable with CID references. Market: TAM $1.2B — theatrical lighting color tools | SAM $250M — digital lighting palette apps | SOM $15M — IPFS palette sharing platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightingPalette Exchange" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and trade curated lighting color palettes as images and data on IPFS for designers to reuse. Discipline: Theater & Live Performance (color study). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s JWT upload ensures palettes are permanently stored and shareable with CID references. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightingPalette Exchange" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-venuemap-index-12 Title: VenueMap Index Theme: Theater & Live Performance (theater) · stage layout Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin detailed venue and stage layout manifests on IPFS for directors and designers to coordinate setups. Why Hedera: Pinata supports pinning detailed JSON layout descriptors accessible for all production stakeholders. Market: TAM $2.3B — venue management software | SAM $400M — digital stage layout tools | SOM $20M — IPFS venue mapping services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VenueMap Index" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin detailed venue and stage layout manifests on IPFS for directors and designers to coordinate setups. Discipline: Theater & Live Performance (stage layout). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata supports pinning detailed JSON layout descriptors accessible for all production stakeholders. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VenueMap Index" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-monologue-archive-13 Title: Monologue Archive Theme: Theater & Live Performance (theater) · performance repertoire Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin curated monologue scripts and performance notes on IPFS preserving artistic intent unchangeably. Why Hedera: Pinata ensures monologue content is stored immutable with permanent CID links accessible worldwide. Market: TAM $1.5B — acting training resources | SAM $300M — digital monologue libraries | SOM $18M — IPFS-based monologue archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Monologue Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin curated monologue scripts and performance notes on IPFS preserving artistic intent unchangeably. Discipline: Theater & Live Performance (performance repertoire). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures monologue content is stored immutable with permanent CID links accessible worldwide. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Monologue Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-costumetexture-bank-14 Title: CostumeTexture Bank Theme: Theater & Live Performance (theater) · fabric study Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin high-resolution fabric texture images and details on IPFS for costume designers’ resource sharing. Why Hedera: Pinata’s IPFS pinning preserves image quality and metadata for collaborative costume fabrics. Market: TAM $1.8B — costume material sourcing | SAM $350M — digital fabric repositories | SOM $22M — IPFS costume texture platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CostumeTexture Bank" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin high-resolution fabric texture images and details on IPFS for costume designers’ resource sharing. Discipline: Theater & Live Performance (fabric study). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning preserves image quality and metadata for collaborative costume fabrics. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CostumeTexture Bank" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-propblueprint-cache-15 Title: PropBlueprint Cache Theme: Theater & Live Performance (theater) · prop design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and share detailed prop blueprints and assembly guides pinned immutably on IPFS for artisans. Why Hedera: Pinata uploads ensure permanence and decentralized access to critical prop design files. Market: TAM $1.7B — prop manufacturing | SAM $320M — digital prop design platforms | SOM $18M — IPFS-based blueprint archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropBlueprint Cache" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and share detailed prop blueprints and assembly guides pinned immutably on IPFS for artisans. Discipline: Theater & Live Performance (prop design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata uploads ensure permanence and decentralized access to critical prop design files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropBlueprint Cache" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-directorstoryboard-hub-16 Title: DirectorStoryboard Hub Theme: Theater & Live Performance (theater) · storyboarding Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin storyboards as image sequences on IPFS to maintain integrity and facilitate collaboration across teams. Why Hedera: Pinata enables permanent IPFS pinning of multi-image storyboards with reliable CID referencing. Market: TAM $2B — theatrical directing tools | SAM $450M — digital storyboarding software | SOM $25M — IPFS storyboard sharing platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DirectorStoryboard Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin storyboards as image sequences on IPFS to maintain integrity and facilitate collaboration across teams. Discipline: Theater & Live Performance (storyboarding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables permanent IPFS pinning of multi-image storyboards with reliable CID referencing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DirectorStoryboard Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightingfixture-db-17 Title: LightingFixture DB Theme: Theater & Live Performance (theater) · equipment catalog Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin detailed lighting fixture profiles and manuals on IPFS for technicians and designers to access anytime. Why Hedera: Pinata secures fixture data immutably with CID links ensuring decentralization and longevity. Market: TAM $3B — theatrical equipment databases | SAM $600M — lighting tech catalog apps | SOM $40M — IPFS-backed fixture info services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightingFixture DB" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin detailed lighting fixture profiles and manuals on IPFS for technicians and designers to access anytime. Discipline: Theater & Live Performance (equipment catalog). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata secures fixture data immutably with CID links ensuring decentralization and longevity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightingFixture DB" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-virtualstage-seeds-18 Title: VirtualStage Seeds Theme: Theater & Live Performance (theater) · virtual set design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin virtual stage assets and scene manifests on IPFS for seamless integration in live and hybrid performances. Why Hedera: Pinata's IPFS pinning provides reliable decentralized storage for virtual stage asset distribution. Market: TAM $2.5B — virtual performance tech | SAM $500M — virtual set design software | SOM $35M — IPFS virtual staging tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VirtualStage Seeds" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin virtual stage assets and scene manifests on IPFS for seamless integration in live and hybrid performances. Discipline: Theater & Live Performance (virtual set design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata's IPFS pinning provides reliable decentralized storage for virtual stage asset distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VirtualStage Seeds" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightingpattern-archive-19 Title: LightingPattern Archive Theme: Theater & Live Performance (theater) · lighting choreography Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and share choreographed lighting patterns and sequences immutably on IPFS for repeatable productions. Why Hedera: Pinata ensures choreography data is stored permanently for consistent performance replication. Market: TAM $1.5B — theatrical lighting choreography | SAM $300M — lighting sequence software | SOM $20M — IPFS-based lighting pattern archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightingPattern Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and share choreographed lighting patterns and sequences immutably on IPFS for repeatable productions. Discipline: Theater & Live Performance (lighting choreography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures choreography data is stored permanently for consistent performance replication. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightingPattern Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-performancemanifesto-20 Title: PerformanceManifesto Theme: Theater & Live Performance (theater) · creative documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin performance manifestos and artistic statements as permanent JSON files on IPFS for posterity and access. Why Hedera: Pinata’s IPFS pinning offers an immutable record for artists’ creative intent and public sharing. Market: TAM $2B — artistic documentation market | SAM $300M — digital artist statement platforms | SOM $15M — IPFS manifesto hosting services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PerformanceManifesto" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin performance manifestos and artistic statements as permanent JSON files on IPFS for posterity and access. Discipline: Theater & Live Performance (creative documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning offers an immutable record for artists’ creative intent and public sharing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PerformanceManifesto" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stagesafety-logs-21 Title: StageSafety Logs Theme: Theater & Live Performance (theater) · safety compliance Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin safety inspection reports and compliance logs on IPFS ensuring tamper-proof records for venues and crews. Why Hedera: Pinata guarantees permanent, tamper-resistant storage of critical safety documentation on IPFS. Market: TAM $1B — theatrical safety management | SAM $200M — digital safety compliance tools | SOM $12M — IPFS-based safety log platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StageSafety Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin safety inspection reports and compliance logs on IPFS ensuring tamper-proof records for venues and crews. Discipline: Theater & Live Performance (safety compliance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata guarantees permanent, tamper-resistant storage of critical safety documentation on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StageSafety Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-actorlinetracker-22 Title: ActorLineTracker Theme: Theater & Live Performance (theater) · line memorization Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin annotated lines and rehearsal notes as immutable JSON to IPFS for actors’ reliable study aids. Why Hedera: Pinata enables secure, permanent pinning of rehearsal content accessible across devices. Market: TAM $1.2B — acting education market | SAM $250M — digital memorization tools | SOM $15M — IPFS actor rehearsal aids ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ActorLineTracker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin annotated lines and rehearsal notes as immutable JSON to IPFS for actors’ reliable study aids. Discipline: Theater & Live Performance (line memorization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables secure, permanent pinning of rehearsal content accessible across devices. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ActorLineTracker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-setlighting-simulator-23 Title: SetLighting Simulator Theme: Theater & Live Performance (theater) · previsualization Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin lighting simulation data and images on IPFS for real-time, distributed production previews. Why Hedera: Pinata’s IPFS storage provides permanent hosting for complex simulation assets shared globally. Market: TAM $2B — performance previsualization | SAM $400M — digital lighting simulator platforms | SOM $25M — IPFS stage lighting simulation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SetLighting Simulator" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin lighting simulation data and images on IPFS for real-time, distributed production previews. Discipline: Theater & Live Performance (previsualization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS storage provides permanent hosting for complex simulation assets shared globally. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SetLighting Simulator" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playbill-archive-24 Title: PlayBill Archive Theme: Theater & Live Performance (theater) · program design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin historic and current playbill designs on IPFS for theatrical heritage and design reference. Why Hedera: Pinata offers permanent, decentralized hosting of program designs as image and JSON manifests. Market: TAM $1B — theatrical print design | SAM $150M — digital program archives | SOM $10M — IPFS playbill archival platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlayBill Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin historic and current playbill designs on IPFS for theatrical heritage and design reference. Discipline: Theater & Live Performance (program design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata offers permanent, decentralized hosting of program designs as image and JSON manifests. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlayBill Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-smartstage-access-0 Title: SmartStage Access Theme: Theater & Live Performance (theater) · audience engagement Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable seamless, gas-free ticketing and VIP experiences via social login and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable hassle-free, onchain ticketing without user gas fees. Market: TAM $8B — global live event ticketing market | SAM $2B — digital ticketing for theater performances | SOM $300M — theaters adopting blockchain ticketing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SmartStage Access" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable seamless, gas-free ticketing and VIP experiences via social login and Hedera's fixed sub-cent fees. Discipline: Theater & Live Performance (audience engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable hassle-free, onchain ticketing without user gas fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SmartStage Access" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scriptshare-vault-1 Title: ScriptShare Vault Theme: Theater & Live Performance (theater) · script collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Facilitate secure, collaborative script revisions with onchain identity and no-fee transactions. Why Hedera: Magic Link email sign-in ensures verified users and Hedera's fixed sub-cent fees enable frictionless edits. Market: TAM $500M — global playwriting markets | SAM $120M — digital script collaboration tools | SOM $25M — theater groups using blockchain collaboration ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptShare Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate secure, collaborative script revisions with onchain identity and no-fee transactions. Discipline: Theater & Live Performance (script collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in ensures verified users and Hedera's fixed sub-cent fees enable frictionless edits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptShare Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-propchain-ledger-2 Title: PropChain Ledger Theme: Theater & Live Performance (theater) · prop provenance Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track and verify theater prop history and ownership with private social access and free transactions. Why Hedera: Magic Link email sign-in secures user access; Hedera's fixed sub-cent fees keeps record updates affordable. Market: TAM $300M — global theater prop markets | SAM $80M — prop rental and sales digitization | SOM $15M — blockchain prop tracking adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PropChain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and verify theater prop history and ownership with private social access and free transactions. Discipline: Theater & Live Performance (prop provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in secures user access; Hedera's fixed sub-cent fees keeps record updates affordable. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PropChain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightcue-sync-3 Title: LightCue Sync Theme: Theater & Live Performance (theater) · lighting design Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collaborate on lighting cues live with instant, gasless onchain updates through social login. Why Hedera: Magic Link email sign-in streamlines designer access; Hedera's fixed sub-cent fees enable real-time updates. Market: TAM $1.2B — global stage lighting equipment and design | SAM $300M — digital lighting design collaboration | SOM $50M — theaters using blockchain for lighting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightCue Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaborate on lighting cues live with instant, gasless onchain updates through social login. Discipline: Theater & Live Performance (lighting design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in streamlines designer access; Hedera's fixed sub-cent fees enable real-time updates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightCue Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-actortrust-ledger-4 Title: ActorTrust Ledger Theme: Theater & Live Performance (theater) · performance credentials Hedera hook: Magic Link email wallet [wallet UX] Pitch: Store and verify actor resumes and roles securely with social login and no gas fees. Why Hedera: Magic Link email sign-in offers identity verification; Hedera's fixed sub-cent fees allow easy credential updates. Market: TAM $15B — global actor management and casting market | SAM $3B — digital acting portfolios | SOM $400M — digital credential verification for actors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ActorTrust Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and verify actor resumes and roles securely with social login and no gas fees. Discipline: Theater & Live Performance (performance credentials). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in offers identity verification; Hedera's fixed sub-cent fees allow easy credential updates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ActorTrust Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-sceneswap-marketplace-5 Title: SceneSwap Marketplace Theme: Theater & Live Performance (theater) · set design exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable gas-free peer-to-peer trading of set designs via social wallet and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in authenticates users; Hedera's fixed sub-cent fees enable fee-less trades. Market: TAM $700M — global set design market | SAM $200M — online set design marketplaces | SOM $30M — blockchain-enabled design exchanges ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneSwap Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable gas-free peer-to-peer trading of set designs via social wallet and Hedera's fixed sub-cent fees. Discipline: Theater & Live Performance (set design exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in authenticates users; Hedera's fixed sub-cent fees enable fee-less trades. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneSwap Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playbill-nfts-6 Title: Playbill NFTs Theme: Theater & Live Performance (theater) · memorabilia Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and distribute collectible digital playbills with embedded social wallet and free minting. Why Hedera: Magic Link email sign-in enables easy user onboarding; Hedera's fixed sub-cent fees remove minting friction. Market: TAM $1B — theater memorabilia market | SAM $300M — digital collectibles sector | SOM $50M — NFT playbill startups ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playbill NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and distribute collectible digital playbills with embedded social wallet and free minting. Discipline: Theater & Live Performance (memorabilia). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in enables easy user onboarding; Hedera's fixed sub-cent fees remove minting friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playbill NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-audiencevote-live-7 Title: AudienceVote Live Theme: Theater & Live Performance (theater) · interactive performances Hedera hook: Magic Link email wallet [wallet UX] Pitch: Let audiences vote live on performance elements using social login and gasless onchain votes. Why Hedera: Magic Link email sign-in verifies unique voters; Hedera's fixed sub-cent fees ensure smooth, no-fee tallying. Market: TAM $4B — global interactive theater experiences | SAM $1B — digital audience participation tech | SOM $150M — blockchain-powered voting in shows ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AudienceVote Live" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Let audiences vote live on performance elements using social login and gasless onchain votes. Discipline: Theater & Live Performance (interactive performances). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in verifies unique voters; Hedera's fixed sub-cent fees ensure smooth, no-fee tallying. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AudienceVote Live" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-backstage-pass-8 Title: Backstage Pass Theme: Theater & Live Performance (theater) · exclusive access Hedera hook: Magic Link email wallet [wallet UX] Pitch: Offer backstage digital passes with seamless social wallet login and free sponsorship tx. Why Hedera: Magic Link email sign-in ensures verified fans; Hedera's fixed sub-cent fees keep access delivery fee-free. Market: TAM $2B — live event VIP access market | SAM $500M — digital VIP experiences | SOM $80M — theaters using blockchain access passes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Backstage Pass" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Offer backstage digital passes with seamless social wallet login and free sponsorship tx. Discipline: Theater & Live Performance (exclusive access). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in ensures verified fans; Hedera's fixed sub-cent fees keep access delivery fee-free. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Backstage Pass" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-rehearsalrecord-9 Title: RehearsalRecord Theme: Theater & Live Performance (theater) · performance archives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely archive rehearsal footage and notes with social wallet authentication and free updates. Why Hedera: Magic Link email sign-in grants creator access; Hedera's fixed sub-cent fees enable unlimited archival without cost. Market: TAM $600M — theater production documentation market | SAM $150M — digital rehearsal tools | SOM $25M — blockchain rehearsal archiving ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RehearsalRecord" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely archive rehearsal footage and notes with social wallet authentication and free updates. Discipline: Theater & Live Performance (performance archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in grants creator access; Hedera's fixed sub-cent fees enable unlimited archival without cost. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RehearsalRecord" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-sponsorspotlight-10 Title: SponsorSpotlight Theme: Theater & Live Performance (theater) · performance sponsorship Hedera hook: Magic Link email wallet [wallet UX] Pitch: Connect sponsors and shows with onchain contracts signed via social login and no gas fees. Why Hedera: Magic Link email sign-in enables trust; Hedera's fixed sub-cent fees streamline contract execution without user costs. Market: TAM $5B — global event sponsorship market | SAM $1.5B — theater sponsorship deals | SOM $200M — blockchain-based sponsorships ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SponsorSpotlight" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Connect sponsors and shows with onchain contracts signed via social login and no gas fees. Discipline: Theater & Live Performance (performance sponsorship). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in enables trust; Hedera's fixed sub-cent fees streamline contract execution without user costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SponsorSpotlight" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-roleswap-network-11 Title: RoleSwap Network Theme: Theater & Live Performance (theater) · cast management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Facilitate secure actor role exchanges and confirmations with social wallets and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in verifies identities; Hedera's fixed sub-cent fees allow gasless role swaps. Market: TAM $10B — global casting and personnel management | SAM $3B — digital casting platforms | SOM $400M — blockchain casting exchanges ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RoleSwap Network" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate secure actor role exchanges and confirmations with social wallets and Hedera's fixed sub-cent fees. Discipline: Theater & Live Performance (cast management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in verifies identities; Hedera's fixed sub-cent fees allow gasless role swaps. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RoleSwap Network" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stagedesign-dao-12 Title: StageDesign DAO Theme: Theater & Live Performance (theater) · design collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create decentralized design collectives with gasless voting and social wallet onboarding. Why Hedera: Magic Link email sign-in ensures verified participation; Hedera's fixed sub-cent fees enable costless DAO governance. Market: TAM $1B — theater design collaboration | SAM $300M — digital creative DAOs | SOM $40M — blockchain DAOs in live arts ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StageDesign DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create decentralized design collectives with gasless voting and social wallet onboarding. Discipline: Theater & Live Performance (design collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in ensures verified participation; Hedera's fixed sub-cent fees enable costless DAO governance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StageDesign DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scripttoken-rights-13 Title: ScriptToken Rights Theme: Theater & Live Performance (theater) · rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage and transfer script rights securely via social wallet and sponsored onchain transactions. Why Hedera: Magic Link email sign-in assures identity; Hedera's fixed sub-cent fees facilitate free rights transfers. Market: TAM $800M — global script rights market | SAM $250M — digital IP management | SOM $30M — blockchain rights transfers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptToken Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and transfer script rights securely via social wallet and sponsored onchain transactions. Discipline: Theater & Live Performance (rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in assures identity; Hedera's fixed sub-cent fees facilitate free rights transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptToken Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-cuepoint-tracker-14 Title: CuePoint Tracker Theme: Theater & Live Performance (theater) · live direction Hedera hook: Magic Link email wallet [wallet UX] Pitch: Provide directors with gasless onchain cue tracking and adjustments via social login. Why Hedera: Magic Link email sign-in manages secure access; Hedera's fixed sub-cent fees enable instant, free cue updates. Market: TAM $900M — live direction tools | SAM $300M — digital stage direction software | SOM $45M — blockchain-powered cue tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CuePoint Tracker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Provide directors with gasless onchain cue tracking and adjustments via social login. Discipline: Theater & Live Performance (live direction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in manages secure access; Hedera's fixed sub-cent fees enable instant, free cue updates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CuePoint Tracker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-actormint-ledger-15 Title: ActorMint Ledger Theme: Theater & Live Performance (theater) · performance NFTs Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint and distribute unique actor NFTs representing roles and performances with gasless minting. Why Hedera: Magic Link email sign-in verifies actors; Hedera's fixed sub-cent fees remove minting friction. Market: TAM $1.5B — global NFT entertainment market | SAM $500M — performance-related NFTs | SOM $70M — blockchain adoption among actors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ActorMint Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and distribute unique actor NFTs representing roles and performances with gasless minting. Discipline: Theater & Live Performance (performance NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in verifies actors; Hedera's fixed sub-cent fees remove minting friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ActorMint Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-setbuild-contracts-16 Title: SetBuild Contracts Theme: Theater & Live Performance (theater) · production agreements Hedera hook: Magic Link email wallet [wallet UX] Pitch: Automate set construction contracts with social wallet signing and no-fee onchain transactions. Why Hedera: Magic Link email sign-in ensures contract parties; Hedera's fixed sub-cent fees enable cost-free contract execution. Market: TAM $2B — theater production services | SAM $600M — digital contract tools | SOM $90M — blockchain contract adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SetBuild Contracts" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate set construction contracts with social wallet signing and no-fee onchain transactions. Discipline: Theater & Live Performance (production agreements). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in ensures contract parties; Hedera's fixed sub-cent fees enable cost-free contract execution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SetBuild Contracts" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lightingtoken-rewards-17 Title: LightingToken Rewards Theme: Theater & Live Performance (theater) · crew incentives Hedera hook: Magic Link email wallet [wallet UX] Pitch: Issue tokenized rewards to lighting crews with social wallet onboarding and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in authenticates recipients; Hedera's fixed sub-cent fees enable frictionless token transfers. Market: TAM $500M — stage crew management | SAM $150M — digital workforce incentives | SOM $20M — blockchain-driven crew rewards ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LightingToken Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue tokenized rewards to lighting crews with social wallet onboarding and Hedera's fixed sub-cent fees. Discipline: Theater & Live Performance (crew incentives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in authenticates recipients; Hedera's fixed sub-cent fees enable frictionless token transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LightingToken Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playwright-guild-18 Title: Playwright Guild Theme: Theater & Live Performance (theater) · community membership Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create tokenized playwright guilds with seamless social login and gasless member transactions. Why Hedera: Magic Link email sign-in simplifies membership; Hedera's fixed sub-cent fees enable costless governance. Market: TAM $400M — playwright associations | SAM $100M — digital community platforms | SOM $15M — blockchain-based guilds ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playwright Guild" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create tokenized playwright guilds with seamless social login and gasless member transactions. Discipline: Theater & Live Performance (community membership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in simplifies membership; Hedera's fixed sub-cent fees enable costless governance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playwright Guild" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-audiencetip-jar-19 Title: AudienceTip Jar Theme: Theater & Live Performance (theater) · fan monetization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Let audiences tip performers instantly with social wallets and Hedera's fixed sub-cent fees gaslessness. Why Hedera: Magic Link email sign-in facilitates fan identity; Hedera's fixed sub-cent fees enable free tips. Market: TAM $3B — live performance monetization | SAM $700M — digital tipping platforms | SOM $100M — blockchain tip solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AudienceTip Jar" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Let audiences tip performers instantly with social wallets and Hedera's fixed sub-cent fees gaslessness. Discipline: Theater & Live Performance (fan monetization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in facilitates fan identity; Hedera's fixed sub-cent fees enable free tips. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AudienceTip Jar" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-promochain-posters-20 Title: PromoChain Posters Theme: Theater & Live Performance (theater) · marketing assets Hedera hook: Magic Link email wallet [wallet UX] Pitch: Distribute limited-edition promo posters as NFTs with easy social wallet minting and free tx. Why Hedera: Magic Link email sign-in verifies fans; Hedera's fixed sub-cent fees remove minting costs. Market: TAM $1B — live show marketing | SAM $300M — digital promotional collectibles | SOM $40M — NFT promo innovations ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PromoChain Posters" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Distribute limited-edition promo posters as NFTs with easy social wallet minting and free tx. Discipline: Theater & Live Performance (marketing assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in verifies fans; Hedera's fixed sub-cent fees remove minting costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PromoChain Posters" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scriptarchive-dao-21 Title: ScriptArchive DAO Theme: Theater & Live Performance (theater) · preservation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Preserve scripts onchain with collaborative DAO management and gasless transactions via social wallets. Why Hedera: Magic Link email sign-in enables decentralized governance; Hedera's fixed sub-cent fees reduce cost barriers. Market: TAM $600M — script preservation | SAM $200M — digital literary archives | SOM $30M — blockchain archival DAOs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptArchive DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Preserve scripts onchain with collaborative DAO management and gasless transactions via social wallets. Discipline: Theater & Live Performance (preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in enables decentralized governance; Hedera's fixed sub-cent fees reduce cost barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptArchive DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-virtualstage-access-22 Title: VirtualStage Access Theme: Theater & Live Performance (theater) · hybrid performances Hedera hook: Magic Link email wallet [wallet UX] Pitch: Grant gasless access to hybrid live/virtual shows via social wallet login and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in simplifies ticketing; Hedera's fixed sub-cent fees enable seamless access without gas. Market: TAM $5B — hybrid live/virtual performances | SAM $1.5B — digital event access | SOM $200M — blockchain ticketing for hybrid shows ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VirtualStage Access" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Grant gasless access to hybrid live/virtual shows via social wallet login and Hedera's fixed sub-cent fees. Discipline: Theater & Live Performance (hybrid performances). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in simplifies ticketing; Hedera's fixed sub-cent fees enable seamless access without gas. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VirtualStage Access" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-critiquechain-feedback-23 Title: CritiqueChain Feedback Theme: Theater & Live Performance (theater) · performance reviews Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collect verified audience critiques onchain with social login and no-fee feedback transactions. Why Hedera: Magic Link email sign-in verifies identities; Hedera's fixed sub-cent fees make feedback free and easy. Market: TAM $700M — performance feedback market | SAM $200M — digital critique platforms | SOM $30M — blockchain feedback adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CritiqueChain Feedback" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collect verified audience critiques onchain with social login and no-fee feedback transactions. Discipline: Theater & Live Performance (performance reviews). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in verifies identities; Hedera's fixed sub-cent fees make feedback free and easy. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CritiqueChain Feedback" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-setinventory-nfts-24 Title: SetInventory NFTs Theme: Theater & Live Performance (theater) · asset tokenization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Tokenize set inventory for easy rental and ownership tracking with social wallets and free tx. Why Hedera: Magic Link email sign-in authenticates owners; Hedera's fixed sub-cent fees allow gasless NFT transfers. Market: TAM $900M — set asset market | SAM $300M — digital asset tokenization | SOM $40M — blockchain asset management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SetInventory NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize set inventory for easy rental and ownership tracking with social wallets and free tx. Discipline: Theater & Live Performance (asset tokenization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in authenticates owners; Hedera's fixed sub-cent fees allow gasless NFT transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SetInventory NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-scene-script-provenance-0 Title: Scene Script Provenance Theme: Theater & Live Performance (theater) · playwriting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and track original play scripts as creator-owned NFTs. Why Hedera: Minting scripts as NFTs ensures immutable proof of authorship and timestamps. Market: TAM $2B — global digital script distribution market | SAM $500M — playwrights adopting digital rights management | SOM $50M — playwrights using NFT-based copyright tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Scene Script Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and track original play scripts as creator-owned NFTs. Discipline: Theater & Live Performance (playwriting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Minting scripts as NFTs ensures immutable proof of authorship and timestamps. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Scene Script Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-lighting-cue-chain-1 Title: Lighting Cue Chain Theme: Theater & Live Performance (theater) · lighting design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Record and verify lighting cue sequences as unique NFTs for live performances. Why Hedera: NFTs provide verifiable provenance for dynamic stage lighting designs. Market: TAM $1B — global stage lighting equipment and software market | SAM $200M — digital lighting design tools | SOM $20M — NFT adoption by lighting professionals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Lighting Cue Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and verify lighting cue sequences as unique NFTs for live performances. Discipline: Theater & Live Performance (lighting design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide verifiable provenance for dynamic stage lighting designs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Lighting Cue Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-actor-rehearsal-logs-2 Title: Actor Rehearsal Logs Theme: Theater & Live Performance (theater) · performance tracking Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Actors mint daily rehearsal videos as NFTs to prove practice and progress. Why Hedera: Onchain minting timestamps rehearsal footage with verified ownership by performers. Market: TAM $1.5B — actor training and rehearsal platforms | SAM $300M — digital performance coaching tools | SOM $30M — NFT solutions for performance documentation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Actor Rehearsal Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Actors mint daily rehearsal videos as NFTs to prove practice and progress. Discipline: Theater & Live Performance (performance tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain minting timestamps rehearsal footage with verified ownership by performers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Actor Rehearsal Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-set-design-archives-3 Title: Set Design Archives Theme: Theater & Live Performance (theater) · scenic design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint 3D set designs as NFTs for secure sharing and historical provenance. Why Hedera: NFTs authenticate original scenic design assets on an immutable ledger. Market: TAM $800M — global set design and prop market | SAM $150M — digital scenic design software | SOM $15M — NFT-based archival tools for set designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Set Design Archives" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint 3D set designs as NFTs for secure sharing and historical provenance. Discipline: Theater & Live Performance (scenic design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs authenticate original scenic design assets on an immutable ledger. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Set Design Archives" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playwright-feedback-chain-4 Title: Playwright Feedback Chain Theme: Theater & Live Performance (theater) · script development Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint feedback and revisions on scripts as NFTs for transparent version control. Why Hedera: NFT provenance tracks iterative script changes with verified contributor ownership. Market: TAM $1.2B — script editing and collaboration tools | SAM $250M — playwright collaboration platforms | SOM $25M — NFT-enabled revision management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playwright Feedback Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint feedback and revisions on scripts as NFTs for transparent version control. Discipline: Theater & Live Performance (script development). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance tracks iterative script changes with verified contributor ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playwright Feedback Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-performance-nft-tickets-5 Title: Performance NFT Tickets Theme: Theater & Live Performance (theater) · audience engagement Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Issue NFT tickets proving legitimate access and unique attendance experience. Why Hedera: NFT tickets provide tamper-proof proof of participation and ownership. Market: TAM $4B — global event ticketing market | SAM $1B — digital ticketing solutions | SOM $100M — NFT ticket use in live performance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Performance NFT Tickets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue NFT tickets proving legitimate access and unique attendance experience. Discipline: Theater & Live Performance (audience engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT tickets provide tamper-proof proof of participation and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Performance NFT Tickets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-costume-provenance-chain-6 Title: Costume Provenance Chain Theme: Theater & Live Performance (theater) · costume design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint costume designs and ownership as NFTs to protect designer rights and provenance. Why Hedera: NFTs secure costume intellectual property with explicit creator ownership. Market: TAM $600M — costume and wardrobe market | SAM $120M — digital costume design assets | SOM $12M — NFT adoption in costume provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Costume Provenance Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint costume designs and ownership as NFTs to protect designer rights and provenance. Discipline: Theater & Live Performance (costume design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs secure costume intellectual property with explicit creator ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Costume Provenance Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-monologue-minting-hub-7 Title: Monologue Minting Hub Theme: Theater & Live Performance (theater) · acting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Actors mint monologues as NFTs to showcase and monetize original performances. Why Hedera: NFTs verify creators’ unique performance content and intellectual property. Market: TAM $700M — actor content creation market | SAM $140M — digital acting portfolios | SOM $14M — NFT monologue platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Monologue Minting Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Actors mint monologues as NFTs to showcase and monetize original performances. Discipline: Theater & Live Performance (acting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs verify creators’ unique performance content and intellectual property. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Monologue Minting Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-soundscape-provenance-8 Title: Soundscape Provenance Theme: Theater & Live Performance (theater) · sound design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint live performance soundscapes as NFTs to authenticate original audio environments. Why Hedera: NFTs provide immutable timestamps and creator proof for sound designs. Market: TAM $900M — live sound engineering market | SAM $180M — digital sound design tools | SOM $18M — NFT soundscape authentication ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Soundscape Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint live performance soundscapes as NFTs to authenticate original audio environments. Discipline: Theater & Live Performance (sound design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide immutable timestamps and creator proof for sound designs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Soundscape Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-director-s-vision-ledger-9 Title: Director's Vision Ledger Theme: Theater & Live Performance (theater) · direction Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Directors mint annotated scripts as NFTs capturing their unique vision and notes. Why Hedera: NFTs record the director’s creative input with provenance and ownership. Market: TAM $1B — digital script annotation market | SAM $200M — director collaboration software | SOM $20M — NFT-based vision tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Director's Vision Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Directors mint annotated scripts as NFTs capturing their unique vision and notes. Discipline: Theater & Live Performance (direction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs record the director’s creative input with provenance and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Director's Vision Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-interactive-stage-nfts-10 Title: Interactive Stage NFTs Theme: Theater & Live Performance (theater) · stage interaction Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint interactive performance elements as NFTs to prove creative ownership. Why Hedera: NFTs authenticate unique interactive stage designs and technology. Market: TAM $500M — interactive theater technology | SAM $100M — interactive stage asset markets | SOM $10M — NFT use in interactive performances ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Stage NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint interactive performance elements as NFTs to prove creative ownership. Discipline: Theater & Live Performance (stage interaction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs authenticate unique interactive stage designs and technology. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Stage NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-improv-moment-mint-11 Title: Improv Moment Mint Theme: Theater & Live Performance (theater) · improvisation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Actors mint spontaneous improv performances as NFTs to preserve unique moments. Why Hedera: NFT provenance captures ephemeral content with verifiable ownership. Market: TAM $300M — improv and live performance recording | SAM $60M — digital improv content | SOM $6M — NFT improv minting platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Improv Moment Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Actors mint spontaneous improv performances as NFTs to preserve unique moments. Discipline: Theater & Live Performance (improvisation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance captures ephemeral content with verifiable ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Improv Moment Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-choreography-chain-12 Title: Choreography Chain Theme: Theater & Live Performance (theater) · movement design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint choreography sequences as NFTs ensuring original dance and movement rights. Why Hedera: NFTs record and timestamp choreography with creator attribution. Market: TAM $1.8B — dance and choreography market | SAM $360M — digital choreography tools | SOM $36M — NFT-based choreography protection ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Choreography Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint choreography sequences as NFTs ensuring original dance and movement rights. Discipline: Theater & Live Performance (movement design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs record and timestamp choreography with creator attribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Choreography Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-stage-props-provenance-13 Title: Stage Props Provenance Theme: Theater & Live Performance (theater) · prop management Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint digital twins of stage props as NFTs for ownership and rental tracking. Why Hedera: NFTs track provenance and leasing history of unique stage props. Market: TAM $700M — theatrical prop rental and sales | SAM $140M — prop rental platforms | SOM $14M — NFT prop provenance solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Stage Props Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint digital twins of stage props as NFTs for ownership and rental tracking. Discipline: Theater & Live Performance (prop management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs track provenance and leasing history of unique stage props. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Stage Props Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-script-translation-nfts-14 Title: Script Translation NFTs Theme: Theater & Live Performance (theater) · translation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint translated scripts as NFTs to protect translators' intellectual property. Why Hedera: NFTs ensure verified ownership of individual translated script versions. Market: TAM $500M — script translation and localization market | SAM $100M — digital translation services | SOM $10M — NFT translation copyright tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Script Translation NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint translated scripts as NFTs to protect translators' intellectual property. Discipline: Theater & Live Performance (translation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs ensure verified ownership of individual translated script versions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Script Translation NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-audience-experience-logs-15 Title: Audience Experience Logs Theme: Theater & Live Performance (theater) · audience analytics Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint audience reaction recordings as NFTs to capture genuine engagement data. Why Hedera: NFTs provide verifiable proof of authentic audience feedback content. Market: TAM $800M — audience engagement analytics market | SAM $160M — digital feedback tools | SOM $16M — NFT feedback platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Audience Experience Logs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint audience reaction recordings as NFTs to capture genuine engagement data. Discipline: Theater & Live Performance (audience analytics). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide verifiable proof of authentic audience feedback content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Audience Experience Logs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-virtual-backstage-pass-16 Title: Virtual Backstage Pass Theme: Theater & Live Performance (theater) · fan engagement Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Issue NFT backstage passes granting exclusive digital and live experience. Why Hedera: NFT passes confirm exclusive access and prevent counterfeit entries. Market: TAM $3B — fan engagement and merchandise market | SAM $600M — digital fan access platforms | SOM $60M — NFT backstage pass adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Backstage Pass" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue NFT backstage passes granting exclusive digital and live experience. Discipline: Theater & Live Performance (fan engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT passes confirm exclusive access and prevent counterfeit entries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Backstage Pass" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playbill-provenance-17 Title: Playbill Provenance Theme: Theater & Live Performance (theater) · program design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint digital playbills as NFTs to preserve historical performance records. Why Hedera: NFTs store immutable playbill data with creator proof. Market: TAM $400M — theater program and souvenir market | SAM $80M — digital playbill platforms | SOM $8M — NFT archival playbill solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playbill Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint digital playbills as NFTs to preserve historical performance records. Discipline: Theater & Live Performance (program design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs store immutable playbill data with creator proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playbill Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-set-lighting-automation-nfts-18 Title: Set Lighting Automation NFTs Theme: Theater & Live Performance (theater) · lighting tech Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint automated lighting sequences as NFTs to secure ownership and resale rights. Why Hedera: NFTs certify original lighting automation and code assets. Market: TAM $1.2B — automated stage lighting market | SAM $240M — digital lighting control software | SOM $24M — NFT automation asset market ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Set Lighting Automation NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint automated lighting sequences as NFTs to secure ownership and resale rights. Discipline: Theater & Live Performance (lighting tech). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs certify original lighting automation and code assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Set Lighting Automation NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-drama-therapy-journals-19 Title: Drama Therapy Journals Theme: Theater & Live Performance (theater) · therapeutic performance Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint personal drama therapy session outputs as NFTs to secure privacy and ownership. Why Hedera: NFTs provide confidential, verifiable proof of therapeutic creative work. Market: TAM $600M — drama therapy and wellness market | SAM $120M — digital therapy documentation | SOM $12M — NFT-secured therapy journals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Drama Therapy Journals" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint personal drama therapy session outputs as NFTs to secure privacy and ownership. Discipline: Theater & Live Performance (therapeutic performance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide confidential, verifiable proof of therapeutic creative work. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Drama Therapy Journals" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-costume-rental-nft-20 Title: Costume Rental NFT Theme: Theater & Live Performance (theater) · rental services Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs representing costume rental agreements to streamline transactions. Why Hedera: NFTs enable trustless rental contracts with provenance and transferability. Market: TAM $900M — costume rental market | SAM $180M — digital rental management platforms | SOM $18M — NFT rental solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Costume Rental NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs representing costume rental agreements to streamline transactions. Discipline: Theater & Live Performance (rental services). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs enable trustless rental contracts with provenance and transferability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Costume Rental NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-playwright-royalty-nfts-21 Title: Playwright Royalty NFTs Theme: Theater & Live Performance (theater) · royalty management Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint royalty shares as NFTs to transparently track playwriting revenue splits. Why Hedera: NFTs allow fractionalized, verifiable royalty ownership and transfers. Market: TAM $1.5B — playwriting royalty market | SAM $300M — digital royalty tracking | SOM $30M — NFT royalty platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Playwright Royalty NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint royalty shares as NFTs to transparently track playwriting revenue splits. Discipline: Theater & Live Performance (royalty management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs allow fractionalized, verifiable royalty ownership and transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Playwright Royalty NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-performance-highlight-reels-22 Title: Performance Highlight Reels Theme: Theater & Live Performance (theater) · promotion Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Actors mint highlight reels as NFTs to prove originality and attract bookings. Why Hedera: NFT provenance confirms authentic content ownership for self-promotion. Market: TAM $1B — digital actor marketing market | SAM $200M — highlight reel platforms | SOM $20M — NFT promotional tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Performance Highlight Reels" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Actors mint highlight reels as NFTs to prove originality and attract bookings. Discipline: Theater & Live Performance (promotion). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance confirms authentic content ownership for self-promotion. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Performance Highlight Reels" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-virtual-set-nfts-23 Title: Virtual Set NFTs Theme: Theater & Live Performance (theater) · digital scenography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint virtual set designs as NFTs for use in augmented and virtual performances. Why Hedera: NFTs authenticate digital stage assets for cross-platform use. Market: TAM $1.3B — virtual and augmented theater market | SAM $260M — digital scenography platforms | SOM $26M — NFT virtual set marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Virtual Set NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint virtual set designs as NFTs for use in augmented and virtual performances. Discipline: Theater & Live Performance (digital scenography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs authenticate digital stage assets for cross-platform use. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Virtual Set NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA theater-script-ownership-registry-24 Title: Script Ownership Registry Theme: Theater & Live Performance (theater) · legal protection Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint scripts as NFTs to create an immutable ownership registry for playwrights. Why Hedera: NFTs provide public, tamper-proof proof of script authorship. Market: TAM $2B — global script copyright management | SAM $400M — playwright legal services | SOM $40M — NFT copyright registries ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Script Ownership Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint scripts as NFTs to create an immutable ownership registry for playwrights. Discipline: Theater & Live Performance (legal protection). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide public, tamper-proof proof of script authorship. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Script Ownership Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Videography & Film videographers, editors, content creators ============================================================================== ------------------------------------------------------------------------------ IDEA video-framechain-auth-0 Title: FrameChain Auth Theme: Videography & Film (video) · video copyright Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Verify and timestamp original video frames onchain to prove authentic content ownership instantly. Why Hedera: Hedera testnet smart contracts securely record and timestamp proofs of originality immutably. Market: TAM $1.1B — global video editing software market | SAM $300M — independent filmmaker copyright tools | SOM $50M — freelance videographers needing proof of ownership ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameChain Auth" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify and timestamp original video frames onchain to prove authentic content ownership instantly. Discipline: Videography & Film (video copyright). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts securely record and timestamp proofs of originality immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameChain Auth" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-editversion-ledger-1 Title: EditVersion Ledger Theme: Videography & Film (video) · edit history Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and verify every edit version of a video project transparently on the blockchain. Why Hedera: Hedera testnet contracts provide immutable version history ensuring transparent edit provenance. Market: TAM $1.1B — video editing software market | SAM $200M — professional video editors version control | SOM $25M — freelance content creators managing revisions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EditVersion Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and verify every edit version of a video project transparently on the blockchain. Discipline: Videography & Film (edit history). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide immutable version history ensuring transparent edit provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EditVersion Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-cliplicense-swap-2 Title: ClipLicense Swap Theme: Videography & Film (video) · licensed assets Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Facilitate secure, onchain licensing and transfer of video clips between creators. Why Hedera: Smart contracts automate licensing terms transparently and enforce payments securely. Market: TAM $1.1B — video content licensing market | SAM $400M — clip asset licensing platforms | SOM $60M — independent content creator licensing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipLicense Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate secure, onchain licensing and transfer of video clips between creators. Discipline: Videography & Film (licensed assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts automate licensing terms transparently and enforce payments securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipLicense Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-colorgrade-nft-3 Title: ColorGrade NFT Theme: Videography & Film (video) · color grading Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create and trade unique onchain color grade presets as NFTs for creative reuse. Why Hedera: Hedera testnet NFTs uniquely identify color presets, enabling verified ownership and trade. Market: TAM $1.1B — color grading tools market | SAM $150M — professional color grading plugins | SOM $20M — freelance colorists and editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorGrade NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and trade unique onchain color grade presets as NFTs for creative reuse. Discipline: Videography & Film (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet NFTs uniquely identify color presets, enabling verified ownership and trade. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorGrade NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-framerate-token-4 Title: FrameRate Token Theme: Videography & Film (video) · video metadata Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Store and share verified video metadata like frame rate and resolution securely onchain. Why Hedera: Hedera testnet contracts guarantee immutable, verifiable metadata storage linked to content. Market: TAM $1.1B — video editing software | SAM $250M — metadata management tools | SOM $15M — indie creators needing trusted metadata ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameRate Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and share verified video metadata like frame rate and resolution securely onchain. Discipline: Videography & Film (video metadata). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts guarantee immutable, verifiable metadata storage linked to content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameRate Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scenesync-chain-5 Title: SceneSync Chain Theme: Videography & Film (video) · collaborative editing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable real-time, onchain synchronization of collaborative video editing sessions. Why Hedera: Smart contracts coordinate edits and track contributions securely on Hedera testnet network. Market: TAM $1.1B — collaborative video software | SAM $100M — cloud-based video editing platforms | SOM $10M — small teams of content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneSync Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable real-time, onchain synchronization of collaborative video editing sessions. Discipline: Videography & Film (collaborative editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts coordinate edits and track contributions securely on Hedera testnet network. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneSync Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-contenttrust-badge-6 Title: ContentTrust Badge Theme: Videography & Film (video) · video authenticity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Award verifiable trust badges onchain for authentic and verified video content. Why Hedera: Hedera testnet contracts issue tamper-proof badges proving content legitimacy and origin. Market: TAM $1.1B — video verification market | SAM $120M — authenticity verification services | SOM $18M — independent journalists and creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ContentTrust Badge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Award verifiable trust badges onchain for authentic and verified video content. Discipline: Videography & Film (video authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts issue tamper-proof badges proving content legitimacy and origin. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ContentTrust Badge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-royaltysplit-dao-7 Title: RoyaltySplit DAO Theme: Videography & Film (video) · creator royalties Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage automatic onchain royalty splits among multiple video collaborators transparently. Why Hedera: Smart contracts enforce programmable payments distributing royalties fairly and instantly. Market: TAM $1.1B — video collaboration tools | SAM $350M — royalty management platforms | SOM $40M — small creator teams and freelancers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "RoyaltySplit DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage automatic onchain royalty splits among multiple video collaborators transparently. Discipline: Videography & Film (creator royalties). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enforce programmable payments distributing royalties fairly and instantly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "RoyaltySplit DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scenetag-registry-8 Title: SceneTag Registry Theme: Videography & Film (video) · scene metadata Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Register and verify scene tags onchain to improve searchable video content metadata. Why Hedera: Hedera testnet contracts ensure immutable, decentralized tagging of video scenes. Market: TAM $1.1B — metadata indexing services | SAM $180M — video metadata enhancement tools | SOM $22M — freelance editors cataloging scenes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneTag Registry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Register and verify scene tags onchain to improve searchable video content metadata. Discipline: Videography & Film (scene metadata). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts ensure immutable, decentralized tagging of video scenes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneTag Registry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scriptchain-ledger-9 Title: ScriptChain Ledger Theme: Videography & Film (video) · screenplay tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Timestamp and verify screenplay versions onchain to protect script authorship claims. Why Hedera: Smart contracts provide immutable proof of screenplay creation and edits. Market: TAM $1.1B — video pre-production software | SAM $90M — scriptwriting and tracking tools | SOM $12M — independent screenwriters and creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptChain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Timestamp and verify screenplay versions onchain to protect script authorship claims. Discipline: Videography & Film (screenplay tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts provide immutable proof of screenplay creation and edits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptChain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clipstake-platform-10 Title: ClipStake Platform Theme: Videography & Film (video) · video staking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Stake tokens on promising clips to support creators and earn rewards via smart contracts. Why Hedera: Hedera testnet contracts automate staking, rewards, and clip performance tracking transparently. Market: TAM $1.1B — video content monetization | SAM $300M — creator crowdfunding platforms | SOM $35M — indie creators seeking funding ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipStake Platform" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Stake tokens on promising clips to support creators and earn rewards via smart contracts. Discipline: Videography & Film (video staking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automate staking, rewards, and clip performance tracking transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipStake Platform" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-autosubtitle-mint-11 Title: AutoSubtitle Mint Theme: Videography & Film (video) · subtitle generation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Generate and store verified subtitles onchain to guarantee transcript authenticity. Why Hedera: Smart contracts timestamp and verify subtitle data immutably on Hedera testnet network. Market: TAM $1.1B — video accessibility tools | SAM $110M — subtitle generation software | SOM $14M — content creators needing verified transcripts ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AutoSubtitle Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Generate and store verified subtitles onchain to guarantee transcript authenticity. Discipline: Videography & Film (subtitle generation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts timestamp and verify subtitle data immutably on Hedera testnet network. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AutoSubtitle Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-videocollab-dao-12 Title: VideoCollab DAO Theme: Videography & Film (video) · collaborative project management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralize video project management via DAOs for transparent decision-making and funding. Why Hedera: Hedera testnet smart contracts enable programmable governance and funding distribution. Market: TAM $1.1B — video collaboration software | SAM $150M — project management for creators | SOM $18M — small creator collectives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VideoCollab DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize video project management via DAOs for transparent decision-making and funding. Discipline: Videography & Film (collaborative project management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable programmable governance and funding distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VideoCollab DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-nftscene-frames-13 Title: NFTScene Frames Theme: Videography & Film (video) · unique video moments Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint key video frames as NFTs to sell exclusive ownership of iconic moments. Why Hedera: NFTs on Hedera testnet uniquely store frame data and provenance securely and transparently. Market: TAM $1.1B — NFT video collectibles | SAM $250M — creator NFT marketplaces | SOM $30M — content creators monetizing moments ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFTScene Frames" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint key video frames as NFTs to sell exclusive ownership of iconic moments. Discipline: Videography & Film (unique video moments). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs on Hedera testnet uniquely store frame data and provenance securely and transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFTScene Frames" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-chainfeedback-loop-14 Title: ChainFeedback Loop Theme: Videography & Film (video) · editorial feedback Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Collect and verify editorial feedback on video edits onchain to improve collaboration. Why Hedera: Hedera testnet contracts provide permanent, auditable records of feedback and approvals. Market: TAM $1.1B — video editing software | SAM $80M — editorial collaboration tools | SOM $10M — freelance editors and directors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ChainFeedback Loop" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collect and verify editorial feedback on video edits onchain to improve collaboration. Discipline: Videography & Film (editorial feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide permanent, auditable records of feedback and approvals. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ChainFeedback Loop" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-sceneunlock-token-15 Title: SceneUnlock Token Theme: Videography & Film (video) · content access control Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Token-gated access to exclusive video scenes ensuring verified viewer entitlements. Why Hedera: Smart contracts control permissioned access via token ownership on Hedera testnet chain. Market: TAM $1.1B — video monetization tools | SAM $220M — gated content platforms | SOM $25M — indie creators selling exclusive scenes ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneUnlock Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Token-gated access to exclusive video scenes ensuring verified viewer entitlements. Discipline: Videography & Film (content access control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts control permissioned access via token ownership on Hedera testnet chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneUnlock Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-colorswap-market-16 Title: ColorSwap Market Theme: Videography & Film (video) · color palette trading Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Trade and license unique color palettes onchain for licensed use in video projects. Why Hedera: Hedera testnet contracts track ownership and licensing of color palettes as digital assets. Market: TAM $1.1B — color grading and design markets | SAM $140M — digital asset marketplaces | SOM $19M — freelance colorists and editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorSwap Market" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade and license unique color palettes onchain for licensed use in video projects. Discipline: Videography & Film (color palette trading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts track ownership and licensing of color palettes as digital assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorSwap Market" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clipproof-archive-17 Title: ClipProof Archive Theme: Videography & Film (video) · immutable clip storage Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Archive important video clips immutably onchain for indisputable provenance and retrieval. Why Hedera: Hedera testnet smart contracts guarantee permanent, tamper-proof clip hash storage. Market: TAM $1.1B — video archival solutions | SAM $130M — clip verification services | SOM $16M — legal and media professionals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipProof Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Archive important video clips immutably onchain for indisputable provenance and retrieval. Discipline: Videography & Film (immutable clip storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts guarantee permanent, tamper-proof clip hash storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipProof Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-livecut-chain-18 Title: LiveCut Chain Theme: Videography & Film (video) · live edit tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track edits and transitions in live video streams securely onchain for transparent records. Why Hedera: Smart contracts record every live edit event immutably on Hedera testnet blockchain. Market: TAM $1.1B — live video production tools | SAM $90M — live editing platforms | SOM $12M — live stream editors and producers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LiveCut Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track edits and transitions in live video streams securely onchain for transparent records. Discipline: Videography & Film (live edit tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts record every live edit event immutably on Hedera testnet blockchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LiveCut Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-captiontag-nft-19 Title: CaptionTag NFT Theme: Videography & Film (video) · caption ownership Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint caption sets as NFTs to prove creative ownership and enable resale. Why Hedera: Hedera testnet NFTs securely represent unique caption data and ownership rights. Market: TAM $1.1B — video editing software | SAM $70M — captioning and subtitle services | SOM $9M — freelance caption creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CaptionTag NFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint caption sets as NFTs to prove creative ownership and enable resale. Discipline: Videography & Film (caption ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet NFTs securely represent unique caption data and ownership rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CaptionTag NFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-framerate-oracle-20 Title: FrameRate Oracle Theme: Videography & Film (video) · metadata validation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Use onchain oracles to validate frame rate data ensuring consistency across platforms. Why Hedera: Hedera testnet smart contracts integrate with oracles for real-time metadata verification. Market: TAM $1.1B — video metadata services | SAM $85M — data verification platforms | SOM $11M — content creators needing metadata accuracy ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameRate Oracle" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Use onchain oracles to validate frame rate data ensuring consistency across platforms. Discipline: Videography & Film (metadata validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts integrate with oracles for real-time metadata verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameRate Oracle" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-reelrights-dao-21 Title: ReelRights DAO Theme: Videography & Film (video) · copyright governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Decentralize video copyright decisions and dispute resolutions via governance DAOs. Why Hedera: Hedera testnet smart contracts enable secure voting and binding governance outcomes. Market: TAM $1.1B — video IP management | SAM $180M — copyright management platforms | SOM $22M — small creator rights groups ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ReelRights DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize video copyright decisions and dispute resolutions via governance DAOs. Discipline: Videography & Film (copyright governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable secure voting and binding governance outcomes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ReelRights DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-cliptip-payments-22 Title: ClipTip Payments Theme: Videography & Film (video) · micro-tipping Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable instant micro-tipping to video creators with onchain transparency and low fees. Why Hedera: Hedera testnet smart contracts automate trustless, low-cost tipping transactions. Market: TAM $1.1B — creator monetization | SAM $310M — micro-payment platforms | SOM $40M — indie video creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipTip Payments" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable instant micro-tipping to video creators with onchain transparency and low fees. Discipline: Videography & Film (micro-tipping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts automate trustless, low-cost tipping transactions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipTip Payments" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-motiontrack-chain-23 Title: MotionTrack Chain Theme: Videography & Film (video) · motion metadata Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record verified motion tracking data onchain for transparent post-production workflows. Why Hedera: Hedera testnet immutable contracts store precise motion metadata securely and verifiably. Market: TAM $1.1B — post-production software | SAM $120M — motion tracking tools | SOM $15M — freelance VFX artists and editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionTrack Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record verified motion tracking data onchain for transparent post-production workflows. Discipline: Videography & Film (motion metadata). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet immutable contracts store precise motion metadata securely and verifiably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionTrack Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scenemint-hub-24 Title: SceneMint Hub Theme: Videography & Film (video) · scene NFT marketplace Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint and trade unique video scenes as NFTs to unlock new revenue streams for creators. Why Hedera: Hedera testnet smart contracts enable secure minting and transferring of scene-based NFTs. Market: TAM $1.1B — NFT video content market | SAM $270M — video NFT marketplaces | SOM $28M — independent content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneMint Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade unique video scenes as NFTs to unlock new revenue streams for creators. Discipline: Videography & Film (scene NFT marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable secure minting and transferring of scene-based NFTs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneMint Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-framesync-ledger-0 Title: FrameSync Ledger Theme: Videography & Film (video) · version control Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track and manage video edits through IPFS to ensure lossless version history for creators. Why Hedera: IPFS provides immutable storage for every edit's metadata, guaranteeing tamper-proof version control. Market: TAM $1.1B — global video editing software market | SAM $250M — collaborative video editing tools segment | SOM $30M — independent content creators needing secure versioning ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameSync Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and manage video edits through IPFS to ensure lossless version history for creators. Discipline: Videography & Film (version control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS provides immutable storage for every edit's metadata, guaranteeing tamper-proof version control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameSync Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clipstamp-vault-1 Title: ClipStamp Vault Theme: Videography & Film (video) · content authentication Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely timestamp video clips onchain to verify original creation times and prevent plagiarism. Why Hedera: Pinata hashes and pins clip metadata on IPFS, providing a decentralized proof of authenticity. Market: TAM $1.1B — video editing and publishing software | SAM $150M — anti-piracy tools for videographers | SOM $20M — freelance video editors needing clip verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipStamp Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely timestamp video clips onchain to verify original creation times and prevent plagiarism. Discipline: Videography & Film (content authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata hashes and pins clip metadata on IPFS, providing a decentralized proof of authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipStamp Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-storyboard-chain-2 Title: Storyboard Chain Theme: Videography & Film (video) · preproduction planning Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create IPFS-backed interactive storyboards that persistently link video plans and references. Why Hedera: Pinata stores storyboards as JSON/manifests ensuring persistent, easily shareable preproduction records. Market: TAM $1.1B — video production software market | SAM $200M — preproduction and planning tools | SOM $25M — indie filmmakers and content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create IPFS-backed interactive storyboards that persistently link video plans and references. Discipline: Videography & Film (preproduction planning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata stores storyboards as JSON/manifests ensuring persistent, easily shareable preproduction records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-colorgrade-provenance-3 Title: ColorGrade Provenance Theme: Videography & Film (video) · color grading Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Track and share editable color grading profiles via IPFS for collaborative and consistent grading. Why Hedera: Pinata’s IPFS pins store immutable color LUTs and metadata accessible by all stakeholders. Market: TAM $1.1B — video editing software industry | SAM $100M — color grading plugin market | SOM $15M — freelance colorists and small studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorGrade Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and share editable color grading profiles via IPFS for collaborative and consistent grading. Discipline: Videography & Film (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pins store immutable color LUTs and metadata accessible by all stakeholders. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorGrade Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-motionmask-archive-4 Title: MotionMask Archive Theme: Videography & Film (video) · masking and effects Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and share masking templates on IPFS to reuse and collaborate on complex video effects. Why Hedera: Pinata’s JWT upload ensures permanent, decentralized storage of masking data and JSON manifests. Market: TAM $1.1B — video effects software market | SAM $120M — masking and rotoscoping tools | SOM $18M — video editors seeking reusable assets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionMask Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and share masking templates on IPFS to reuse and collaborate on complex video effects. Discipline: Videography & Film (masking and effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s JWT upload ensures permanent, decentralized storage of masking data and JSON manifests. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionMask Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-soundsync-chain-5 Title: SoundSync Chain Theme: Videography & Film (video) · audio synchronization Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Synchronize and store audio-video alignment data on IPFS for seamless collaborative editing. Why Hedera: Pinata pins synchronization metadata ensuring permanent, tamper-proof storage accessible to all. Market: TAM $1.1B — video and audio editing software | SAM $130M — audio synchronization tools | SOM $22M — independent content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SoundSync Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Synchronize and store audio-video alignment data on IPFS for seamless collaborative editing. Discipline: Videography & Film (audio synchronization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins synchronization metadata ensuring permanent, tamper-proof storage accessible to all. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SoundSync Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-cutlist-ledger-6 Title: CutList Ledger Theme: Videography & Film (video) · editing workflows Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and track cut decisions and timelines on IPFS for transparent editing history and audit trails. Why Hedera: Pinata guarantees immutable storage of timeline JSON manifests, improving workflow accountability. Market: TAM $1.1B — video editing software market | SAM $110M — professional editing workflow tools | SOM $16M — freelance editors requiring auditability ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CutList Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and track cut decisions and timelines on IPFS for transparent editing history and audit trails. Discipline: Videography & Film (editing workflows). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata guarantees immutable storage of timeline JSON manifests, improving workflow accountability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CutList Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-vfx-asset-chain-7 Title: VFX Asset Chain Theme: Videography & Film (video) · visual effects Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Decentralize and share VFX assets metadata on IPFS to streamline collaborative asset management. Why Hedera: Pinata stores immutable asset manifests on IPFS, ensuring persistent availability across teams. Market: TAM $1.1B — video effects and compositing software | SAM $140M — VFX asset marketplaces | SOM $20M — independent VFX artists and studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VFX Asset Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize and share VFX assets metadata on IPFS to streamline collaborative asset management. Discipline: Videography & Film (visual effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata stores immutable asset manifests on IPFS, ensuring persistent availability across teams. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VFX Asset Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-captioncast-ipfs-8 Title: CaptionCast IPFS Theme: Videography & Film (video) · subtitling and captions Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and distribute subtitle files on IPFS for reliable, versioned caption accessibility worldwide. Why Hedera: Pinata’s IPFS pinning ensures subtitles are immutable, accessible, and easily shared across platforms. Market: TAM $1.1B — video editing and streaming software | SAM $90M — captioning and subtitling tools | SOM $12M — content creators needing accessible captions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CaptionCast IPFS" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and distribute subtitle files on IPFS for reliable, versioned caption accessibility worldwide. Discipline: Videography & Film (subtitling and captions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning ensures subtitles are immutable, accessible, and easily shared across platforms. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CaptionCast IPFS" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-transcode-manifest-9 Title: Transcode Manifest Theme: Videography & Film (video) · video encoding Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin transcoding manifests on IPFS to track formats, resolutions, and settings transparently. Why Hedera: Pinata guarantees permanent, tamper-proof storage of transcoding metadata for reliable versioning. Market: TAM $1.1B — video encoding and editing market | SAM $100M — transcoding software segment | SOM $14M — video producers managing multi-format outputs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Transcode Manifest" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin transcoding manifests on IPFS to track formats, resolutions, and settings transparently. Discipline: Videography & Film (video encoding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata guarantees permanent, tamper-proof storage of transcoding metadata for reliable versioning. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Transcode Manifest" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-loopprovenance-10 Title: LoopProvenance Theme: Videography & Film (video) · animation loops Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store looped animation metadata on IPFS to verify originality and share reusable loop assets. Why Hedera: Pinata’s JWT upload pins loop descriptors immutably, enabling persistent provenance proof. Market: TAM $1.1B — video editing and animation tools | SAM $80M — animation asset marketplaces | SOM $10M — animators and motion graphic creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LoopProvenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store looped animation metadata on IPFS to verify originality and share reusable loop assets. Discipline: Videography & Film (animation loops). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s JWT upload pins loop descriptors immutably, enabling persistent provenance proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LoopProvenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scriptsync-chain-11 Title: ScriptSync Chain Theme: Videography & Film (video) · script and dialogue Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and link script revisions and annotations on IPFS for collaborative screenplay editing. Why Hedera: Pinata ensures immutable, accessible script JSON manifests, improving team collaboration. Market: TAM $1.1B — video production software market | SAM $95M — scriptwriting and planning tools | SOM $13M — indie filmmakers and content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptSync Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and link script revisions and annotations on IPFS for collaborative screenplay editing. Discipline: Videography & Film (script and dialogue). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures immutable, accessible script JSON manifests, improving team collaboration. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptSync Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-lensprofile-archive-12 Title: LensProfile Archive Theme: Videography & Film (video) · camera profiling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Decentralize camera and lens profiles on IPFS for consistent color and exposure correction. Why Hedera: Pinata stores immutable camera profile JSON data accessible by all editing software. Market: TAM $1.1B — video post-production software | SAM $85M — camera profiling tools | SOM $11M — freelance videographers and editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LensProfile Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Decentralize camera and lens profiles on IPFS for consistent color and exposure correction. Discipline: Videography & Film (camera profiling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata stores immutable camera profile JSON data accessible by all editing software. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LensProfile Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-assetaudit-trail-13 Title: AssetAudit Trail Theme: Videography & Film (video) · media asset management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin asset usage logs and metadata on IPFS to audit rights and permissions transparently. Why Hedera: Pinata immutably stores JSON manifests of asset provenance, securing usage compliance. Market: TAM $1.1B — digital asset management software | SAM $120M — media rights management tools | SOM $17M — video editors managing licensed content ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AssetAudit Trail" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin asset usage logs and metadata on IPFS to audit rights and permissions transparently. Discipline: Videography & Film (media asset management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata immutably stores JSON manifests of asset provenance, securing usage compliance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AssetAudit Trail" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-filterforge-ipfs-14 Title: FilterForge IPFS Theme: Videography & Film (video) · filter development Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Share and version custom video filters on IPFS for easy collaboration and reuse. Why Hedera: Pinata pins filter JSON manifests ensuring permanent availability and provenance. Market: TAM $1.1B — video editing software market | SAM $90M — video filter plugin segment | SOM $12M — content creators needing custom filters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FilterForge IPFS" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share and version custom video filters on IPFS for easy collaboration and reuse. Discipline: Videography & Film (filter development). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins filter JSON manifests ensuring permanent availability and provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FilterForge IPFS" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clipchain-marketplace-15 Title: ClipChain Marketplace Theme: Videography & Film (video) · clip licensing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create decentralized marketplaces for licensed video clips pinned immutably on IPFS. Why Hedera: Pinata’s permanent pinning suits immutable clip metadata storage for transparent licensing. Market: TAM $1.1B — video content marketplace industry | SAM $200M — stock footage licensing platforms | SOM $30M — independent clip creators and buyers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipChain Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create decentralized marketplaces for licensed video clips pinned immutably on IPFS. Discipline: Videography & Film (clip licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s permanent pinning suits immutable clip metadata storage for transparent licensing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipChain Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-timelinetrace-ipfs-16 Title: TimelineTrace IPFS Theme: Videography & Film (video) · editing timeline Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin entire editing timelines on IPFS to enable persistent sharing and rollback across projects. Why Hedera: Pinata stores immutable timeline manifests ensuring long-term availability and version tracking. Market: TAM $1.1B — video editing software market | SAM $135M — collaborative timeline tools | SOM $18M — freelance video editors needing timeline backups ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TimelineTrace IPFS" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin entire editing timelines on IPFS to enable persistent sharing and rollback across projects. Discipline: Videography & Film (editing timeline). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata stores immutable timeline manifests ensuring long-term availability and version tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TimelineTrace IPFS" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-frameforge-ipfs-17 Title: FrameForge IPFS Theme: Videography & Film (video) · frame-specific notes Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Attach and store notes and metadata to individual frames on IPFS for detailed editorial feedback. Why Hedera: Pinata pins JSON frame annotations immutably, enabling collaborative frame-level feedback. Market: TAM $1.1B — video editing and production software | SAM $100M — editorial collaboration tools | SOM $14M — post-production teams and editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameForge IPFS" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach and store notes and metadata to individual frames on IPFS for detailed editorial feedback. Discipline: Videography & Film (frame-specific notes). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins JSON frame annotations immutably, enabling collaborative frame-level feedback. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameForge IPFS" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-effectchain-library-18 Title: EffectChain Library Theme: Videography & Film (video) · effect presets Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and share effect preset libraries on IPFS for repeatable, consistent video stylizations. Why Hedera: Pinata stores immutable effect preset manifests easily distributed to creators. Market: TAM $1.1B — video effects software market | SAM $110M — effect preset marketplaces | SOM $16M — freelance editors and content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EffectChain Library" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and share effect preset libraries on IPFS for repeatable, consistent video stylizations. Discipline: Videography & Film (effect presets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata stores immutable effect preset manifests easily distributed to creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EffectChain Library" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-posterchain-assets-19 Title: PosterChain Assets Theme: Videography & Film (video) · promotional imagery Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and share film poster and thumbnail assets on IPFS for consistent branding across platforms. Why Hedera: Pinata’s IPFS pinning secures permanent access to promotional image assets. Market: TAM $1.1B — video marketing and editing software | SAM $85M — marketing asset storage solutions | SOM $10M — independent creators promoting video content ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PosterChain Assets" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and share film poster and thumbnail assets on IPFS for consistent branding across platforms. Discipline: Videography & Film (promotional imagery). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning secures permanent access to promotional image assets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PosterChain Assets" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-eventsync-ledger-20 Title: EventSync Ledger Theme: Videography & Film (video) · live editing events Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and synchronize live edit events on IPFS for verifiable broadcast and post-event review. Why Hedera: Pinata immutable logs of event JSON manifests ensure tamper-proof edit event histories. Market: TAM $1.1B — live video editing software market | SAM $90M — live broadcast editing tools | SOM $12M — live content creators and editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EventSync Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and synchronize live edit events on IPFS for verifiable broadcast and post-event review. Discipline: Videography & Film (live editing events). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata immutable logs of event JSON manifests ensure tamper-proof edit event histories. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EventSync Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-transitionchain-vault-21 Title: TransitionChain Vault Theme: Videography & Film (video) · transition effects Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store reusable video transition presets on IPFS to ensure permanent, shareable effects. Why Hedera: Pinata pins transition data immutably, facilitating collaboration and versioning. Market: TAM $1.1B — video editing and effects software | SAM $85M — transition preset market | SOM $11M — freelance editors creating transition assets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TransitionChain Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store reusable video transition presets on IPFS to ensure permanent, shareable effects. Discipline: Videography & Film (transition effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins transition data immutably, facilitating collaboration and versioning. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TransitionChain Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-metadatamesh-ipfs-22 Title: MetadataMesh IPFS Theme: Videography & Film (video) · metadata management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin enriched metadata sets on IPFS to enhance archival, search, and retrieval of video content. Why Hedera: Pinata ensures permanent, decentralized storage of metadata JSON manifests for reliable access. Market: TAM $1.1B — video content management software | SAM $105M — metadata enrichment tools | SOM $14M — content creators managing large video libraries ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MetadataMesh IPFS" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin enriched metadata sets on IPFS to enhance archival, search, and retrieval of video content. Discipline: Videography & Film (metadata management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures permanent, decentralized storage of metadata JSON manifests for reliable access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MetadataMesh IPFS" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-shotlist-ledger-23 Title: ShotList Ledger Theme: Videography & Film (video) · production tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin shot lists and scene breakdowns on IPFS for secure, shared production references. Why Hedera: Pinata immutable pins guarantee persistent access to shot and scene manifests. Market: TAM $1.1B — film production software market | SAM $95M — production planning tools | SOM $13M — indie filmmakers and video producers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ShotList Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin shot lists and scene breakdowns on IPFS for secure, shared production references. Discipline: Videography & Film (production tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata immutable pins guarantee persistent access to shot and scene manifests. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ShotList Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-seamless-clip-shares-0 Title: Seamless Clip Shares Theme: Videography & Film (video) · collaborative editing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share video edits instantly without gas fees, enabling smooth collaboration among creators. Why Hedera: Magic Link email sign-in + Hedera's fixed sub-cent fees allow frictionless user sign-in and gasless transaction sharing. Market: TAM $1.1B — global video editing software market | SAM $200M — collaborative video editing tools segment | SOM $15M — early adopters of gasless collaborative editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Seamless Clip Shares" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share video edits instantly without gas fees, enabling smooth collaboration among creators. Discipline: Videography & Film (collaborative editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in + Hedera's fixed sub-cent fees allow frictionless user sign-in and gasless transaction sharing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Seamless Clip Shares" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-tokenized-cut-approval-1 Title: Tokenized Cut Approval Theme: Videography & Film (video) · edit review Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable editors and clients to approve cuts securely via onchain, without gas barriers. Why Hedera: Gasless Hedera's fixed sub-cent fees ensure approval actions are user-friendly and trackable onchain. Market: TAM $1.1B — global video editing software market | SAM $100M — client-facing editing workflows | SOM $7M — video production teams adopting blockchain reviews ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Cut Approval" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable editors and clients to approve cuts securely via onchain, without gas barriers. Discipline: Videography & Film (edit review). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Gasless Hedera's fixed sub-cent fees ensure approval actions are user-friendly and trackable onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Cut Approval" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clip-provenance-ledger-2 Title: Clip Provenance Ledger Theme: Videography & Film (video) · authenticity tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Verify original footage sources with onchain provenance that users access gaslessly. Why Hedera: Magic Link email sign-in simplifies provenance verification with no gas friction. Market: TAM $1.1B — video editing and content creation market | SAM $80M — authenticity and rights management segment | SOM $5M — creators integrating footage provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Clip Provenance Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Verify original footage sources with onchain provenance that users access gaslessly. Discipline: Videography & Film (authenticity tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in simplifies provenance verification with no gas friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Clip Provenance Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-sponsored-effects-marketplace-3 Title: Sponsored Effects Marketplace Theme: Videography & Film (video) · visual effects trading Hedera hook: Magic Link email wallet [wallet UX] Pitch: Buy and sell video effects licenses seamlessly with gasless wallet transactions. Why Hedera: Magic Link email sign-ins with Hedera's fixed sub-cent fees remove gas complexity for effect licensing. Market: TAM $1.1B — video editing software ecosystem | SAM $150M — effects plugin marketplace | SOM $10M — users transacting effects onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Effects Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Buy and sell video effects licenses seamlessly with gasless wallet transactions. Discipline: Videography & Film (visual effects trading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins with Hedera's fixed sub-cent fees remove gas complexity for effect licensing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Effects Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gasless-frame-tokens-4 Title: Gasless Frame Tokens Theme: Videography & Film (video) · NFT video frames Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint and trade unique video frames as NFTs without ever paying gas fees. Why Hedera: Hedera's fixed sub-cent fees enable zero-cost NFT minting and transfers for all users. Market: TAM $1.1B — video editing and NFT markets | SAM $120M — NFT collectibles in video creators community | SOM $8M — early adopters minting video frame NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Frame Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade unique video frames as NFTs without ever paying gas fees. Discipline: Videography & Film (NFT video frames). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees enable zero-cost NFT minting and transfers for all users. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Frame Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-instant-sponsor-rewards-5 Title: Instant Sponsor Rewards Theme: Videography & Film (video) · creator monetization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward fans instantly onchain with tokens during video interactions without gas hurdles. Why Hedera: Hedera's fixed sub-cent fees and Google sign-in facilitate smooth reward issuance and wallet onboarding. Market: TAM $1.1B — video monetization platforms | SAM $200M — fan reward and tipping services | SOM $12M — creators adopting onchain reward systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Instant Sponsor Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward fans instantly onchain with tokens during video interactions without gas hurdles. Discipline: Videography & Film (creator monetization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees and Google sign-in facilitate smooth reward issuance and wallet onboarding. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Instant Sponsor Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-collaborative-scene-ledger-6 Title: Collaborative Scene Ledger Theme: Videography & Film (video) · scene co-creation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Track scene contributions by multiple creators transparently with zero gas cost onchain. Why Hedera: the embedded wallet’s gasless Hedera's fixed sub-cent fees ensure seamless multi-user scene input recording. Market: TAM $1.1B — video editing and collaboration tools | SAM $90M — collaborative film production software | SOM $6M — teams using onchain scene management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Scene Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track scene contributions by multiple creators transparently with zero gas cost onchain. Discipline: Videography & Film (scene co-creation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s gasless Hedera's fixed sub-cent fees ensure seamless multi-user scene input recording. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Scene Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-script-to-screen-sync-7 Title: Script-to-Screen Sync Theme: Videography & Film (video) · production alignment Hedera hook: Magic Link email wallet [wallet UX] Pitch: Sync script changes and video edits onchain for teams without forcing gas payments. Why Hedera: Magic Link email sign-in’s gasless tx make real-time script and edit synchronization user-friendly. Market: TAM $1.1B — film and video editing workflow tools | SAM $70M — script and production coordination software | SOM $4M — studios using onchain syncing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Script-to-Screen Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sync script changes and video edits onchain for teams without forcing gas payments. Discipline: Videography & Film (production alignment). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in’s gasless tx make real-time script and edit synchronization user-friendly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Script-to-Screen Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gasless-licensing-hub-8 Title: Gasless Licensing Hub Theme: Videography & Film (video) · rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: License stock footage instantly with zero gas fees for hassle-free transactions. Why Hedera: Hedera's fixed sub-cent fees simplify licensing exchanges without user gas cost concerns. Market: TAM $1.1B — video stock and licensing markets | SAM $130M — digital content licensing platforms | SOM $9M — users adopting gasless licensing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Licensing Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License stock footage instantly with zero gas fees for hassle-free transactions. Discipline: Videography & Film (rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees simplify licensing exchanges without user gas cost concerns. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Licensing Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-verified-creator-ids-9 Title: Verified Creator IDs Theme: Videography & Film (video) · identity verification Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable filmmakers to verify credentials onchain with easy sign-in and no gas fees. Why Hedera: Magic Link email sign-in's Google sign-in plus Hedera's fixed sub-cent fees secures ID verification smoothly. Market: TAM $1.1B — creative professional identity solutions | SAM $60M — video creator verification services | SOM $3M — creators onboarding with onchain IDs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Verified Creator IDs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable filmmakers to verify credentials onchain with easy sign-in and no gas fees. Discipline: Videography & Film (identity verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in's Google sign-in plus Hedera's fixed sub-cent fees secures ID verification smoothly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Verified Creator IDs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-sponsored-review-tokens-10 Title: Sponsored Review Tokens Theme: Videography & Film (video) · audience feedback Hedera hook: Magic Link email wallet [wallet UX] Pitch: Viewers reward video feedback with tokens instantly, without seeing transaction fees. Why Hedera: Magic Link email sign-in with gasless tx enables frictionless token reward distribution. Market: TAM $1.1B — video platforms with viewer engagement | SAM $110M — audience tokenization features | SOM $7M — creators using onchain feedback tokens ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Review Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Viewers reward video feedback with tokens instantly, without seeing transaction fees. Discipline: Videography & Film (audience feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with gasless tx enables frictionless token reward distribution. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Review Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-chain-backed-storyboards-11 Title: Chain-Backed Storyboards Theme: Videography & Film (video) · previsualization Hedera hook: Magic Link email wallet [wallet UX] Pitch: Store and share editable storyboards onchain with instant, gas-free updates. Why Hedera: Hedera's fixed sub-cent fees allow real-time storyboard collaboration without gas user barrier. Market: TAM $1.1B — video pre-production software | SAM $85M — storyboard collaboration tools | SOM $5M — teams using onchain storyboarding ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Backed Storyboards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and share editable storyboards onchain with instant, gas-free updates. Discipline: Videography & Film (previsualization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees allow real-time storyboard collaboration without gas user barrier. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Backed Storyboards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gasless-release-commits-12 Title: Gasless Release Commits Theme: Videography & Film (video) · version control Hedera hook: Magic Link email wallet [wallet UX] Pitch: Commit video edits onchain transparently with zero gas cost for creators. Why Hedera: the embedded wallet Hedera's fixed sub-cent fees enable seamless, gasless version tracking. Market: TAM $1.1B — video editing software market | SAM $95M — version control and asset tracking | SOM $6M — editors adopting onchain commits ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Release Commits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Commit video edits onchain transparently with zero gas cost for creators. Discipline: Videography & Film (version control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Hedera's fixed sub-cent fees enable seamless, gasless version tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Release Commits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-tokenized-b-roll-access-13 Title: Tokenized B-Roll Access Theme: Videography & Film (video) · footage licensing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Grant and revoke B-roll access quickly via token ownership with no gas fees. Why Hedera: the embedded wallet gasless wallet enables instant token-based permission management. Market: TAM $1.1B — video licensing and distribution | SAM $75M — B-roll licensing platforms | SOM $4M — users transacting tokenized access ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized B-Roll Access" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Grant and revoke B-roll access quickly via token ownership with no gas fees. Discipline: Videography & Film (footage licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet gasless wallet enables instant token-based permission management. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized B-Roll Access" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-sponsored-collab-invites-14 Title: Sponsored Collab Invites Theme: Videography & Film (video) · team management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Invite collaborators to projects through gas-free, onchain wallet interactions. Why Hedera: the embedded wallet’s Google sign-in and Hedera's fixed sub-cent fees streamline access without gas complexity. Market: TAM $1.1B — creative collaboration software | SAM $140M — team management tools | SOM $9M — video teams using onchain invites ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Collab Invites" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Invite collaborators to projects through gas-free, onchain wallet interactions. Discipline: Videography & Film (team management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet’s Google sign-in and Hedera's fixed sub-cent fees streamline access without gas complexity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Collab Invites" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-privy-clip-bounties-15 Title: the embedded wallet Clip Bounties Theme: Videography & Film (video) · crowdsourced footage Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward contributors for clip submissions instantly with gas-free sponsor transactions. Why Hedera: Hedera's fixed sub-cent fees enable instant, frictionless bounty payouts to contributors. Market: TAM $1.1B — crowdsourced content platforms | SAM $100M — footage crowdsourcing and reward systems | SOM $6M — communities paying out onchain bounties ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "the embedded wallet Clip Bounties" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward contributors for clip submissions instantly with gas-free sponsor transactions. Discipline: Videography & Film (crowdsourced footage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees enable instant, frictionless bounty payouts to contributors. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "the embedded wallet Clip Bounties" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-chain-stamped-edits-16 Title: Chain-Stamped Edits Theme: Videography & Film (video) · edit authenticity Hedera hook: Magic Link email wallet [wallet UX] Pitch: Stamp finalized edits onchain instantly to prove authenticity without gas hassles. Why Hedera: the embedded wallet Hedera's fixed sub-cent fees ensure cost-free, verifiable edit timestamping. Market: TAM $1.1B — video editing and authenticity tools | SAM $90M — edit verification software | SOM $5M — creators adopting onchain stamping ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Stamped Edits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Stamp finalized edits onchain instantly to prove authenticity without gas hassles. Discipline: Videography & Film (edit authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Hedera's fixed sub-cent fees ensure cost-free, verifiable edit timestamping. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Stamped Edits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gasless-asset-sync-17 Title: Gasless Asset Sync Theme: Videography & Film (video) · media syncing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Sync media assets across devices and collaborators onchain with no gas fees. Why Hedera: Magic Link email sign-ins with gasless transactions remove syncing friction for users. Market: TAM $1.1B — video production asset management | SAM $115M — media syncing and sharing tools | SOM $8M — users using onchain syncing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Asset Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sync media assets across devices and collaborators onchain with no gas fees. Discipline: Videography & Film (media syncing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins with gasless transactions remove syncing friction for users. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Asset Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-token-gate-premieres-18 Title: Token Gate Premieres Theme: Videography & Film (video) · exclusive screenings Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host video premieres gated by token ownership with smooth, gasless access. Why Hedera: Sponsor tx enable premium access control without requiring users to pay gas. Market: TAM $1.1B — video content distribution | SAM $125M — premium video event platforms | SOM $9M — creators hosting token-gated premieres ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Token Gate Premieres" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host video premieres gated by token ownership with smooth, gasless access. Discipline: Videography & Film (exclusive screenings). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Sponsor tx enable premium access control without requiring users to pay gas. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Token Gate Premieres" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-sponsored-clip-challenges-19 Title: Sponsored Clip Challenges Theme: Videography & Film (video) · user engagement Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create video challenges rewarding participation instantly with no gas transaction fees. Why Hedera: the embedded wallet gasless tx promote seamless challenge reward distribution and onboarding. Market: TAM $1.1B — video engagement and competition markets | SAM $95M — user-generated contest platforms | SOM $6M — creators running gasless clip contests ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Clip Challenges" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create video challenges rewarding participation instantly with no gas transaction fees. Discipline: Videography & Film (user engagement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet gasless tx promote seamless challenge reward distribution and onboarding. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Clip Challenges" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gasless-comment-tokens-20 Title: Gasless Comment Tokens Theme: Videography & Film (video) · community feedback Hedera hook: Magic Link email wallet [wallet UX] Pitch: Reward insightful video comments with tokens instantly without charging gas fees. Why Hedera: the embedded wallet Hedera's fixed sub-cent fees enable smooth token rewards for community engagement. Market: TAM $1.1B — video comment and community tools | SAM $75M — token-enabled comment systems | SOM $4M — active users rewarding comments ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Comment Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward insightful video comments with tokens instantly without charging gas fees. Discipline: Videography & Film (community feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Hedera's fixed sub-cent fees enable smooth token rewards for community engagement. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Comment Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-chain-linked-shot-lists-21 Title: Chain-Linked Shot Lists Theme: Videography & Film (video) · production planning Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage shot lists collaboratively onchain without gas fees for production teams. Why Hedera: the embedded wallet's gasless tx aid real-time, user-friendly shot list updates. Market: TAM $1.1B — film production software market | SAM $70M — shot list and production planning tools | SOM $3M — teams using onchain shot management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Chain-Linked Shot Lists" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage shot lists collaboratively onchain without gas fees for production teams. Discipline: Videography & Film (production planning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet's gasless tx aid real-time, user-friendly shot list updates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Chain-Linked Shot Lists" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-sponsored-render-credits-22 Title: Sponsored Render Credits Theme: Videography & Film (video) · compute resource sharing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Trade video render credits instantly via gasless onchain transactions. Why Hedera: Magic Link email sign-ins with Hedera's fixed sub-cent fees facilitate smooth render credit exchanges. Market: TAM $1.1B — rendering service markets | SAM $100M — video render credit trading | SOM $7M — users exchanging render tokens ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sponsored Render Credits" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Trade video render credits instantly via gasless onchain transactions. Discipline: Videography & Film (compute resource sharing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins with Hedera's fixed sub-cent fees facilitate smooth render credit exchanges. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sponsored Render Credits" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gasless-frame-annotations-23 Title: Gasless Frame Annotations Theme: Videography & Film (video) · video feedback Hedera hook: Magic Link email wallet [wallet UX] Pitch: Annotate and share feedback on frames instantly with gasless transaction support. Why Hedera: the embedded wallet Hedera's fixed sub-cent fees remove gas cost barriers for annotation updates. Market: TAM $1.1B — video editing and review markets | SAM $80M — annotation and feedback tools | SOM $5M — users adopting onchain annotation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gasless Frame Annotations" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Annotate and share feedback on frames instantly with gasless transaction support. Discipline: Videography & Film (video feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Hedera's fixed sub-cent fees remove gas cost barriers for annotation updates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gasless Frame Annotations" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-tokenized-music-sync-24 Title: Tokenized Music Sync Theme: Videography & Film (video) · audio licensing Hedera hook: Magic Link email wallet [wallet UX] Pitch: License music for videos via tokens with zero gas fees and seamless sign-in. Why Hedera: the embedded wallet gasless tx simplify music sync licensing for creators and publishers. Market: TAM $1.1B — video and music licensing industries | SAM $140M — sync licensing platforms | SOM $10M — users transacting music licenses ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Tokenized Music Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT License music for videos via tokens with zero gas fees and seamless sign-in. Discipline: Videography & Film (audio licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet gasless tx simplify music sync licensing for creators and publishers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Tokenized Music Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-framelock-provenance-0 Title: FrameLock Provenance Theme: Videography & Film (video) · shot authentication Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely prove original shot ownership for video editors and creators to prevent unauthorized reuse. Why Hedera: NFT provenance minting uniquely timestamps and attributes original video frames onchain for irrefutable proof. Market: TAM $1.1B — global video editing software market | SAM $300M — professional video editors and studios | SOM $20M — early adopter videographers valuing ownership verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameLock Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely prove original shot ownership for video editors and creators to prevent unauthorized reuse. Discipline: Videography & Film (shot authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance minting uniquely timestamps and attributes original video frames onchain for irrefutable proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameLock Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-colorgrade-ledger-1 Title: ColorGrade Ledger Theme: Videography & Film (video) · color grading Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track color correction versions with creator-owned tokens to maintain authentic grading history. Why Hedera: NFT provenance records immutable color grade IPFS files, ensuring transparent visual edits. Market: TAM $1.1B — video editing software industry | SAM $200M — colorists and post-production studios | SOM $15M — freelance color grading professionals ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorGrade Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track color correction versions with creator-owned tokens to maintain authentic grading history. Discipline: Videography & Film (color grading). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance records immutable color grade IPFS files, ensuring transparent visual edits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorGrade Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clipchain-remix-2 Title: ClipChain Remix Theme: Videography & Film (video) · video remixing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint provenance tokens for remixed clips to credit original and derivative creators fairly. Why Hedera: HTS NFT NFT minting verifies remix lineage and enforces creator ownership onchain. Market: TAM $1.1B — editing and remix software market | SAM $400M — content creators engaging in remix culture | SOM $25M — top remix-focused video editors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipChain Remix" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint provenance tokens for remixed clips to credit original and derivative creators fairly. Discipline: Videography & Film (video remixing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT NFT minting verifies remix lineage and enforces creator ownership onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipChain Remix" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scenereveal-rights-3 Title: SceneReveal Rights Theme: Videography & Film (video) · scene licensing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Onchain minting proves scene ownership, simplifying licensing and reuse agreements for creators. Why Hedera: Immutable NFT provenance ensures transparent rights and licensing traces for each scene. Market: TAM $1.1B — global creative licensing market | SAM $150M — independent filmmakers and content licensors | SOM $10M — niche indie filmmakers seeking secure licensing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneReveal Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Onchain minting proves scene ownership, simplifying licensing and reuse agreements for creators. Discipline: Videography & Film (scene licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFT provenance ensures transparent rights and licensing traces for each scene. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneReveal Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-storyboard-stamp-4 Title: Storyboard Stamp Theme: Videography & Film (video) · previsualization Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint storyboards as NFT tokens to secure creative vision and enable easy sharing with teams. Why Hedera: NFT minting onchain timestamps storyboards with IPFS CIDs, verifying original creative concepts. Market: TAM $1.1B — video production previsualization tools | SAM $100M — film studios and directors | SOM $8M — indie filmmakers and video creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Stamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint storyboards as NFT tokens to secure creative vision and enable easy sharing with teams. Discipline: Videography & Film (previsualization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting onchain timestamps storyboards with IPFS CIDs, verifying original creative concepts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Stamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-edittrace-ledger-5 Title: EditTrace Ledger Theme: Videography & Film (video) · editing history Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track and mint every edit step as NFTs to maintain transparent video edit workflows. Why Hedera: Onchain tokenization captures immutable edit provenance with detailed IPFS metadata. Market: TAM $1.1B — video editing software ecosystem | SAM $250M — professional video editors | SOM $18M — studios demanding workflow auditability ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EditTrace Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and mint every edit step as NFTs to maintain transparent video edit workflows. Discipline: Videography & Film (editing history). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain tokenization captures immutable edit provenance with detailed IPFS metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EditTrace Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-clipmint-archival-6 Title: ClipMint Archival Theme: Videography & Film (video) · archival footage Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint archival footage as NFTs to certify origin and enable monetization of historic clips. Why Hedera: NFT provenance ensures authenticity and immutable proof of archival IPFS-hosted clips. Market: TAM $1.1B — video content archival market | SAM $180M — documentary filmmakers and archivists | SOM $12M — niche archive footage creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ClipMint Archival" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint archival footage as NFTs to certify origin and enable monetization of historic clips. Discipline: Videography & Film (archival footage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures authenticity and immutable proof of archival IPFS-hosted clips. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ClipMint Archival" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-scriptproof-token-7 Title: ScriptProof Token Theme: Videography & Film (video) · script authenticity Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure scripts as creator-owned NFTs to prevent plagiarism and prove original authorship. Why Hedera: NFT minting immutably links scripts on IPFS with creators via HTS NFT tokens. Market: TAM $1.1B — film and video preproduction market | SAM $90M — screenplay writers and script consultants | SOM $6M — independent screenwriters seeking copyright proof ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptProof Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure scripts as creator-owned NFTs to prevent plagiarism and prove original authorship. Discipline: Videography & Film (script authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting immutably links scripts on IPFS with creators via HTS NFT tokens. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptProof Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-moodboard-mint-8 Title: Moodboard Mint Theme: Videography & Film (video) · visual concepting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFT-backed moodboards to validate and share artistic vision securely with collaborators. Why Hedera: NFT provenance anchors moodboard IPFS CIDs to creators, enabling trusted sharing. Market: TAM $1.1B — creative asset management sector | SAM $120M — video directors and concept artists | SOM $7M — small teams using NFT for creative validation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Moodboard Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFT-backed moodboards to validate and share artistic vision securely with collaborators. Discipline: Videography & Film (visual concepting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance anchors moodboard IPFS CIDs to creators, enabling trusted sharing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Moodboard Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-soundsync-provenance-9 Title: SoundSync Provenance Theme: Videography & Film (video) · audio synchronization Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint sound edits as NFTs linked to videos to verify audio-visual synchronization authenticity. Why Hedera: HTS NFT NFTs trace audio IPFS files onchain, certifying sync integrity for creators. Market: TAM $1.1B — video and audio postproduction market | SAM $140M — sound editors and mixers | SOM $9M — indie video producers needing audio proof ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SoundSync Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint sound edits as NFTs linked to videos to verify audio-visual synchronization authenticity. Discipline: Videography & Film (audio synchronization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT NFTs trace audio IPFS files onchain, certifying sync integrity for creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SoundSync Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-titletrack-tokens-10 Title: TitleTrack Tokens Theme: Videography & Film (video) · title animation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique title animations NFTs to protect and share original animated intros. Why Hedera: NFT provenance records title animation IPFS assets with creator ownership onchain. Market: TAM $1.1B — title and motion graphics software market | SAM $110M — graphic designers and video editors | SOM $8M — creators specializing in animated titles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TitleTrack Tokens" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique title animations NFTs to protect and share original animated intros. Discipline: Videography & Film (title animation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance records title animation IPFS assets with creator ownership onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TitleTrack Tokens" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-lensflare-ledger-11 Title: LensFlare Ledger Theme: Videography & Film (video) · visual effects Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Prove originality of lens flare effects by minting creator-owned tokens of effect presets. Why Hedera: Onchain NFT minting timestamps and attributes unique VFX IPFS content securely. Market: TAM $1.1B — visual effects software industry | SAM $130M — VFX artists and studios | SOM $10M — freelance visual effects creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "LensFlare Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Prove originality of lens flare effects by minting creator-owned tokens of effect presets. Discipline: Videography & Film (visual effects). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFT minting timestamps and attributes unique VFX IPFS content securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "LensFlare Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-cutsequence-proof-12 Title: CutSequence Proof Theme: Videography & Film (video) · editing sequence Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure entire editing sequences as NFTs to confirm edit ownership and version control. Why Hedera: HTS NFT tokens contain IPFS CIDs of sequences, providing transparent edit authenticity. Market: TAM $1.1B — video editing and workflow tools | SAM $230M — professional video editing teams | SOM $17M — studios adopting blockchain provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CutSequence Proof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure entire editing sequences as NFTs to confirm edit ownership and version control. Discipline: Videography & Film (editing sequence). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens contain IPFS CIDs of sequences, providing transparent edit authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CutSequence Proof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-filterforge-token-13 Title: FilterForge Token Theme: Videography & Film (video) · video filters Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint original video filter presets as NFTs to protect and monetize unique creative effects. Why Hedera: NFT provenance links IPFS-hosted filter data with creator identity onchain. Market: TAM $1.1B — video effect and filter market | SAM $160M — content creators using filters | SOM $11M — filter developers launching NFT ownership ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FilterForge Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint original video filter presets as NFTs to protect and monetize unique creative effects. Discipline: Videography & Film (video filters). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance links IPFS-hosted filter data with creator identity onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FilterForge Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-vlogproof-mint-14 Title: VlogProof Mint Theme: Videography & Film (video) · content authenticity Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate vlogs via NFT tokens to assure viewers of original creator ownership and date. Why Hedera: Onchain NFT minting links vlogging IPFS content to creator wallets for proof. Market: TAM $1.1B — vlogging and influencer video market | SAM $500M — online video content creators | SOM $30M — vloggers emphasizing authenticity ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VlogProof Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate vlogs via NFT tokens to assure viewers of original creator ownership and date. Discipline: Videography & Film (content authenticity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFT minting links vlogging IPFS content to creator wallets for proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VlogProof Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-motionmap-token-15 Title: MotionMap Token Theme: Videography & Film (video) · motion tracking Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint motion tracking data as NFTs to verify originality and usage rights. Why Hedera: NFT provenance timestamps IPFS motion data securely onchain, ensuring ownership. Market: TAM $1.1B — video editing and motion tracking | SAM $150M — postproduction studios and editors | SOM $10M — independent motion tracking specialists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MotionMap Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint motion tracking data as NFTs to verify originality and usage rights. Discipline: Videography & Film (motion tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance timestamps IPFS motion data securely onchain, ensuring ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MotionMap Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-overlay-origin-16 Title: Overlay Origin Theme: Videography & Film (video) · graphic overlays Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Protect original graphic overlays by minting each as immutable NFTs with creator attribution. Why Hedera: HTS NFT tokens timestamp overlay IPFS files securely under creator control. Market: TAM $1.1B — video graphics and overlay tools | SAM $140M — graphic designers for video | SOM $9M — freelancers selling overlays as NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Overlay Origin" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Protect original graphic overlays by minting each as immutable NFTs with creator attribution. Discipline: Videography & Film (graphic overlays). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens timestamp overlay IPFS files securely under creator control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Overlay Origin" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-subtitle-signet-17 Title: Subtitle Signet Theme: Videography & Film (video) · captioning Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint subtitles as NFTs to certify translation accuracy and original captioning ownership. Why Hedera: Onchain NFT minting links subtitle IPFS files to creators, ensuring verifiable origin. Market: TAM $1.1B — video captioning and localization market | SAM $100M — translators and caption editors | SOM $7M — niche subtitle creators with blockchain proof ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Subtitle Signet" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint subtitles as NFTs to certify translation accuracy and original captioning ownership. Discipline: Videography & Film (captioning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFT minting links subtitle IPFS files to creators, ensuring verifiable origin. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Subtitle Signet" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-trailertrace-mint-18 Title: TrailerTrace Mint Theme: Videography & Film (video) · promotional cuts Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint promotional trailers as NFTs to confirm authenticity and release dates. Why Hedera: NFT provenance records trailer IPFS CIDs onchain, linking to official creators. Market: TAM $1.1B — film marketing and trailer production | SAM $120M — marketing teams and indie filmmakers | SOM $9M — creators seeking verified trailer ownership ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TrailerTrace Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint promotional trailers as NFTs to confirm authenticity and release dates. Discipline: Videography & Film (promotional cuts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance records trailer IPFS CIDs onchain, linking to official creators. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TrailerTrace Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-gift-provenance-19 Title: GIFt Provenance Theme: Videography & Film (video) · animated GIFs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create creator-owned NFT tokens for animated GIFs to protect and monetize viral clips. Why Hedera: NFT minting timestamps GIF IPFS assets, ensuring unique ownership onchain. Market: TAM $1.1B — short video and GIF markets | SAM $300M — social media content creators | SOM $20M — viral creators focusing on NFT protection ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "GIFt Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create creator-owned NFT tokens for animated GIFs to protect and monetize viral clips. Discipline: Videography & Film (animated GIFs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting timestamps GIF IPFS assets, ensuring unique ownership onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "GIFt Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-dronefootage-token-20 Title: DroneFootage Token Theme: Videography & Film (video) · aerial videography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint drone videos as provenance NFTs to secure unique aerial shot copyrights. Why Hedera: HTS NFT tokens verify IPFS-hosted drone footage authenticity on Hedera testnet. Market: TAM $1.1B — drone videography and editing | SAM $180M — professional drone videographers | SOM $12M — freelance aerial content creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DroneFootage Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint drone videos as provenance NFTs to secure unique aerial shot copyrights. Discipline: Videography & Film (aerial videography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens verify IPFS-hosted drone footage authenticity on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DroneFootage Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-timelapse-token-21 Title: TimeLapse Token Theme: Videography & Film (video) · time-lapse videography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure time-lapse video originality by minting as NFTs with immutable timestamps. Why Hedera: NFT provenance mints IPFS time-lapse assets, certifying long-duration capture ownership. Market: TAM $1.1B — time-lapse and specialty video market | SAM $90M — niche videographers and content creators | SOM $6M — independent time-lapse producers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TimeLapse Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure time-lapse video originality by minting as NFTs with immutable timestamps. Discipline: Videography & Film (time-lapse videography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mints IPFS time-lapse assets, certifying long-duration capture ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TimeLapse Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-multicam-mint-22 Title: Multicam Mint Theme: Videography & Film (video) · multi-camera editing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint synchronized multicam edits as NFTs to prove complex edit ownership and authenticity. Why Hedera: Onchain HTS NFT tokens link multicam IPFS metadata to editors transparently. Market: TAM $1.1B — professional video editing software | SAM $210M — multicam editors and studios | SOM $14M — freelance editors adopting blockchain provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Multicam Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint synchronized multicam edits as NFTs to prove complex edit ownership and authenticity. Discipline: Videography & Film (multi-camera editing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain HTS NFT tokens link multicam IPFS metadata to editors transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Multicam Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-tutorialtoken-vault-23 Title: TutorialToken Vault Theme: Videography & Film (video) · educational content Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint tutorial videos as NFTs to certify original teaching content and secure creator rights. Why Hedera: NFT provenance anchors IPFS-hosted tutorials onchain to creators securely. Market: TAM $1.1B — online education video market | SAM $400M — video educators and instructors | SOM $25M — independent tutorial creators using NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "TutorialToken Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint tutorial videos as NFTs to certify original teaching content and secure creator rights. Discipline: Videography & Film (educational content). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance anchors IPFS-hosted tutorials onchain to creators securely. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "TutorialToken Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA video-360proof-token-24 Title: 360Proof Token Theme: Videography & Film (video) · 360-degree videography Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Secure authenticity of 360° videos by minting creator-owned provenance NFTs. Why Hedera: HTS NFT mints IPFS 360° video data proving ownership and originality. Market: TAM $1.1B — immersive video and VR content market | SAM $150M — 360° video producers | SOM $10M — small studios specializing in immersive content ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "360Proof Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure authenticity of 360° videos by minting creator-owned provenance NFTs. Discipline: Videography & Film (360-degree videography). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT mints IPFS 360° video data proving ownership and originality. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "360Proof Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Visual Art painters, illustrators, generative artists, gallerists ============================================================================== ------------------------------------------------------------------------------ IDEA visual-art-provenance-palette-0 Title: Provenance Palette Theme: Visual Art (visual-art) · art ownership tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely track and prove the ownership lineage of paintings with tamper-proof blockchain records. Why Hedera: Hedera testnet smart contracts provide immutable provenance records verified on-chain. Market: TAM $65B — global art market needing ownership verification | SAM $10B — marketplace for authenticated paintings worldwide | SOM $500M — early adopters using blockchain provenance solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Provenance Palette" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely track and prove the ownership lineage of paintings with tamper-proof blockchain records. Discipline: Visual Art (art ownership tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide immutable provenance records verified on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Provenance Palette" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-generative-mint-studio-1 Title: Generative Mint Studio Theme: Visual Art (visual-art) · generative art minting Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Artists can mint unique generative art pieces directly onto Hedera testnet with automated smart contracts. Why Hedera: Hedera testnet contracts enable onchain minting with verifiable randomness and ownership. Market: TAM $1B — blockchain-based generative art market | SAM $150M — active generative artists minting NFTs | SOM $20M — users adopting Hedera testnet for minting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Generative Mint Studio" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Artists can mint unique generative art pieces directly onto Hedera testnet with automated smart contracts. Discipline: Visual Art (generative art minting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable onchain minting with verifiable randomness and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Generative Mint Studio" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-exhibit-chain-ledger-2 Title: Exhibit Chain Ledger Theme: Visual Art (visual-art) · gallery exhibition tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record every artwork's exhibition history securely and transparently onchain for galleries and collectors. Why Hedera: Hedera testnet smart contracts provide decentralized, timestamped exhibition records. Market: TAM $5B — global gallery and exhibition market | SAM $1B — art galleries integrating digital tracking | SOM $100M — digital provenance adopters in galleries ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Exhibit Chain Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record every artwork's exhibition history securely and transparently onchain for galleries and collectors. Discipline: Visual Art (gallery exhibition tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide decentralized, timestamped exhibition records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Exhibit Chain Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-colorcode-certifier-3 Title: ColorCode Certifier Theme: Visual Art (visual-art) · color authenticity validation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Certify and verify exact color compositions of artworks using blockchain color code hashes. Why Hedera: Hedera testnet contracts guarantee immutable storage of color code data linked to artist hashes. Market: TAM $65B — art market reliant on color fidelity | SAM $500M — artists and collectors valuing color authenticity | SOM $30M — initial blockchain-based color certification users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorCode Certifier" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Certify and verify exact color compositions of artworks using blockchain color code hashes. Discipline: Visual Art (color authenticity validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts guarantee immutable storage of color code data linked to artist hashes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorCode Certifier" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-auction-trust-chain-4 Title: Auction Trust Chain Theme: Visual Art (visual-art) · art auction transparency Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable transparent, verifiable auction processes and bids for visual art onchain. Why Hedera: Hedera testnet contracts enforce fair bid logic and record bids immutably. Market: TAM $15B — global art auction market volume | SAM $3B — online art auction platforms | SOM $250M — blockchain auction services adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Auction Trust Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable transparent, verifiable auction processes and bids for visual art onchain. Discipline: Visual Art (art auction transparency). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enforce fair bid logic and record bids immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Auction Trust Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-brushstroke-timestamp-5 Title: Brushstroke Timestamp Theme: Visual Art (visual-art) · creative process timestamping Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Document and timestamp artists’ brushstroke progress live onchain for proof of creation. Why Hedera: Hedera testnet contracts provide reliable, immutable time-stamped events during creation. Market: TAM $65B — art market valuing originality proof | SAM $200M — artists interested in process authentication | SOM $10M — early userbase for onchain creation logs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Brushstroke Timestamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Document and timestamp artists’ brushstroke progress live onchain for proof of creation. Discipline: Visual Art (creative process timestamping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide reliable, immutable time-stamped events during creation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Brushstroke Timestamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-licensing-ledger-6 Title: Licensing Ledger Theme: Visual Art (visual-art) · artwork license management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage and enforce artwork licensing rights transparently with blockchain smart contracts. Why Hedera: Hedera testnet contracts automate license terms and enforce royalties securely onchain. Market: TAM $2B — global art licensing market | SAM $500M — digital art licensing platforms | SOM $50M — blockchain-based licensing solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Licensing Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and enforce artwork licensing rights transparently with blockchain smart contracts. Discipline: Visual Art (artwork license management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automate license terms and enforce royalties securely onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Licensing Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-interactive-canvas-dao-7 Title: Interactive Canvas DAO Theme: Visual Art (visual-art) · collaborative art governance Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable collective decision-making on collaborative visual art projects via decentralized onchain voting. Why Hedera: Hedera testnet smart contracts support secure DAO governance with transparent vote tallying. Market: TAM $100M — collaborative digital art initiatives | SAM $30M — artists using decentralized governance tools | SOM $5M — DAO experiment adopters in art ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Canvas DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable collective decision-making on collaborative visual art projects via decentralized onchain voting. Discipline: Visual Art (collaborative art governance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts support secure DAO governance with transparent vote tallying. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Canvas DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-swap-protocol-8 Title: Art Swap Protocol Theme: Visual Art (visual-art) · peer-to-peer art exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create a trustless platform for artists and collectors to swap artworks instantly onchain. Why Hedera: Hedera testnet contracts enable atomic swaps guaranteeing secure, simultaneous asset exchanges. Market: TAM $1B — peer-to-peer art trading market | SAM $200M — active visual art traders | SOM $25M — early users adopting blockchain swaps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Swap Protocol" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a trustless platform for artists and collectors to swap artworks instantly onchain. Discipline: Visual Art (peer-to-peer art exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable atomic swaps guaranteeing secure, simultaneous asset exchanges. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Swap Protocol" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-edition-tracker-9 Title: Edition Tracker Theme: Visual Art (visual-art) · limited print tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track edition numbers and print runs of limited artworks securely and transparently onchain. Why Hedera: Hedera testnet contracts provide immutable tracking of edition metadata and supply limits. Market: TAM $6B — limited edition print market | SAM $1.2B — collectors focusing on print authenticity | SOM $80M — users transitioning to blockchain edition tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Edition Tracker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track edition numbers and print runs of limited artworks securely and transparently onchain. Discipline: Visual Art (limited print tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide immutable tracking of edition metadata and supply limits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Edition Tracker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-commission-chain-10 Title: Art Commission Chain Theme: Visual Art (visual-art) · commission workflow management Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Streamline and secure commission contracts and payments between artists and patrons using smart contracts. Why Hedera: Hedera testnet smart contracts automate escrow and milestone payments trustlessly. Market: TAM $3B — global art commission market | SAM $800M — platforms facilitating digital commissions | SOM $60M — early blockchain adoption for commissions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Commission Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Streamline and secure commission contracts and payments between artists and patrons using smart contracts. Discipline: Visual Art (commission workflow management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts automate escrow and milestone payments trustlessly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Commission Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-dynamic-frame-nfts-11 Title: Dynamic Frame NFTs Theme: Visual Art (visual-art) · programmable artwork framing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create NFTs that change frames or borders based on onchain conditions or events. Why Hedera: Hedera testnet contracts enable dynamic metadata updates triggering visual changes in real-time. Market: TAM $500M — programmable art NFT market | SAM $150M — collectors seeking interactive art experiences | SOM $20M — initial adopters of dynamic NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dynamic Frame NFTs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs that change frames or borders based on onchain conditions or events. Discipline: Visual Art (programmable artwork framing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable dynamic metadata updates triggering visual changes in real-time. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dynamic Frame NFTs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-gallery-access-token-12 Title: Gallery Access Token Theme: Visual Art (visual-art) · event admission tokens Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue access tokens for exclusive gallery events and exhibitions secured on blockchain. Why Hedera: Hedera testnet contracts provide verifiable, non-transferable access control tokens onchain. Market: TAM $2B — global gallery event ticket market | SAM $600M — digital ticketing for art events | SOM $40M — blockchain-enabled ticket users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gallery Access Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue access tokens for exclusive gallery events and exhibitions secured on blockchain. Discipline: Visual Art (event admission tokens). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide verifiable, non-transferable access control tokens onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gallery Access Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-critique-chain-13 Title: Art Critique Chain Theme: Visual Art (visual-art) · peer review logging Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Record artist peer critiques and feedback permanently and transparently onchain. Why Hedera: Hedera testnet smart contracts ensure immutable and censorship-resistant critique logs. Market: TAM $500M — peer review platforms globally | SAM $120M — digital visual art communities | SOM $10M — blockchain critique tool users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Critique Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record artist peer critiques and feedback permanently and transparently onchain. Discipline: Visual Art (peer review logging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts ensure immutable and censorship-resistant critique logs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Critique Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-signature-stamp-14 Title: Signature Stamp Theme: Visual Art (visual-art) · artist signature authentication Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Cryptographically verify artist signatures on digital and physical artworks using blockchain stamps. Why Hedera: Hedera testnet contracts store immutable signature proofs linked to artworks. Market: TAM $65B — art authentication services | SAM $2B — signature verification market | SOM $100M — blockchain adoption for signature validation ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Signature Stamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Cryptographically verify artist signatures on digital and physical artworks using blockchain stamps. Discipline: Visual Art (artist signature authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts store immutable signature proofs linked to artworks. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Signature Stamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-interactive-auction-game-15 Title: Interactive Auction Game Theme: Visual Art (visual-art) · gamified art auctions Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Turn art auctions into engaging games with transparent rules enforced by onchain smart contracts. Why Hedera: Hedera testnet contracts enable trustless, rule-based game mechanics for auctions. Market: TAM $15B — art auction global market | SAM $500M — gamified auction platforms | SOM $40M — users engaging in blockchain auctions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Auction Game" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Turn art auctions into engaging games with transparent rules enforced by onchain smart contracts. Discipline: Visual Art (gamified art auctions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable trustless, rule-based game mechanics for auctions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Auction Game" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-material-usage-ledger-16 Title: Material Usage Ledger Theme: Visual Art (visual-art) · supply chain transparency Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and verify the ethical sourcing and usage of materials in visual artworks onchain. Why Hedera: Hedera testnet contracts provide immutable supply chain transparency for art materials. Market: TAM $3B — ethical art material market | SAM $700M — collectors prioritizing ethical sourcing | SOM $30M — blockchain material traceability adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Material Usage Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and verify the ethical sourcing and usage of materials in visual artworks onchain. Discipline: Visual Art (supply chain transparency). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide immutable supply chain transparency for art materials. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Material Usage Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-visual-royalties-manager-17 Title: Visual Royalties Manager Theme: Visual Art (visual-art) · artist royalty automation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate royalty payments to artists via smart contracts every time artwork changes hands. Why Hedera: Hedera testnet contracts enforce automatic royalty splits transparently and reliably. Market: TAM $1.5B — global art royalty market | SAM $400M — digital art sales platforms | SOM $50M — blockchain royalty users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Visual Royalties Manager" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate royalty payments to artists via smart contracts every time artwork changes hands. Discipline: Visual Art (artist royalty automation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enforce automatic royalty splits transparently and reliably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Visual Royalties Manager" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-moodboard-dao-18 Title: MoodBoard DAO Theme: Visual Art (visual-art) · collective inspiration curation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Artists collaboratively curate and govern mood boards for projects using decentralized governance. Why Hedera: Hedera testnet contracts facilitate transparent proposal voting and curation rewards. Market: TAM $120M — collaborative art projects | SAM $25M — digital collaboration tools for artists | SOM $3M — DAO tools adoption in art ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MoodBoard DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Artists collaboratively curate and govern mood boards for projects using decentralized governance. Discipline: Visual Art (collective inspiration curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts facilitate transparent proposal voting and curation rewards. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MoodBoard DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-sketch-snapshot-chain-19 Title: Sketch Snapshot Chain Theme: Visual Art (visual-art) · digital sketch archiving Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Instantly archive and timestamp digital sketches onchain for secure creative journaling. Why Hedera: Hedera testnet smart contracts provide immutable and decentralized snapshot storage proofs. Market: TAM $1B — digital art tools market | SAM $200M — digital sketch app users | SOM $15M — early blockchain integration adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sketch Snapshot Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Instantly archive and timestamp digital sketches onchain for secure creative journaling. Discipline: Visual Art (digital sketch archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts provide immutable and decentralized snapshot storage proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sketch Snapshot Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-canvas-rental-token-20 Title: Canvas Rental Token Theme: Visual Art (visual-art) · art space access Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Tokenize and manage temporary rental of studio or gallery spaces for artists seamlessly onchain. Why Hedera: Hedera testnet contracts enable secure timed access with programmable expiration and transfers. Market: TAM $500M — global artist workspace market | SAM $120M — short term art space rentals | SOM $8M — blockchain-based rental platform users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Canvas Rental Token" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize and manage temporary rental of studio or gallery spaces for artists seamlessly onchain. Discipline: Visual Art (art space access). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable secure timed access with programmable expiration and transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Canvas Rental Token" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-nft-curation-index-21 Title: NFT Curation Index Theme: Visual Art (visual-art) · curated NFT collections Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create and manage curated NFT visual art collections with transparent onchain voting and tracking. Why Hedera: Hedera testnet contracts enable decentralized curation and provenance validation. Market: TAM $2B — NFT art collectible market | SAM $600M — active curators and collectors | SOM $50M — users onchain curation platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Curation Index" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and manage curated NFT visual art collections with transparent onchain voting and tracking. Discipline: Visual Art (curated NFT collections). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable decentralized curation and provenance validation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Curation Index" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-brushstroke-dao-vault-22 Title: Brushstroke DAO Vault Theme: Visual Art (visual-art) · collective art funding Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Pool resources in decentralized vaults to fund promising visual art projects transparently. Why Hedera: Hedera testnet contracts allow secure fund management and voting by contributors. Market: TAM $500M — art crowd-funding market | SAM $150M — decentralized funding adopters | SOM $12M — early DAO art fund users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Brushstroke DAO Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pool resources in decentralized vaults to fund promising visual art projects transparently. Discipline: Visual Art (collective art funding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts allow secure fund management and voting by contributors. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Brushstroke DAO Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-swap-escrow-23 Title: Art Swap Escrow Theme: Visual Art (visual-art) · secure art trades Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Allow artists and collectors to trade artworks using blockchain escrow ensuring transaction safety. Why Hedera: Hedera testnet contracts provide trustless escrow with automatic release upon conditions met. Market: TAM $1B — art trading market | SAM $300M — online art trade platforms | SOM $30M — blockchain escrow adopters in art ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Swap Escrow" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Allow artists and collectors to trade artworks using blockchain escrow ensuring transaction safety. Discipline: Visual Art (secure art trades). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide trustless escrow with automatic release upon conditions met. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Swap Escrow" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-visual-art-rewards-24 Title: Visual Art Rewards Theme: Visual Art (visual-art) · artist incentivization Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Reward artists and illustrators for community engagement or milestones via onchain token incentives. Why Hedera: Hedera testnet smart contracts manage transparent and automated reward distributions. Market: TAM $200M — artist monetization platforms | SAM $50M — community-driven artist platforms | SOM $5M — initial blockchain reward users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Visual Art Rewards" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward artists and illustrators for community engagement or milestones via onchain token incentives. Discipline: Visual Art (artist incentivization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts manage transparent and automated reward distributions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Visual Art Rewards" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-permacolor-archive-0 Title: PermaColor Archive Theme: Visual Art (visual-art) · color palette curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Secure and share your unique color palettes as permanent IPFS references. Why Hedera: Pinata JWT ensures immutable storage and easy retrieval of palette metadata on IPFS. Market: TAM $1B — global digital artist tools market | SAM $250M — color-centric art resource platforms | SOM $50M — paid subscriptions for curated palette archives ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PermaColor Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Secure and share your unique color palettes as permanent IPFS references. Discipline: Visual Art (color palette curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures immutable storage and easy retrieval of palette metadata on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PermaColor Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-generative-art-vault-1 Title: Generative Art Vault Theme: Visual Art (visual-art) · algorithmic artwork storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin generative art scripts and outputs permanently to preserve creative provenance. Why Hedera: IPFS via Pinata guarantees unalterable storage of code and images with CID verification. Market: TAM $3B — global generative art market | SAM $700M — platforms hosting generative art | SOM $150M — premium archival and licensing services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Generative Art Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin generative art scripts and outputs permanently to preserve creative provenance. Discipline: Visual Art (algorithmic artwork storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata guarantees unalterable storage of code and images with CID verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Generative Art Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-gallery-manifest-hub-2 Title: Gallery Manifest Hub Theme: Visual Art (visual-art) · exhibition cataloging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create and pin permanent digital manifests for art gallery exhibitions with immutable records. Why Hedera: Pinata enables reliable, permanent IPFS pinning of JSON manifests for galleries’ provenance needs. Market: TAM $5B — global gallery management software | SAM $1.2B — digital exhibition catalog platforms | SOM $300M — galleries adopting blockchain provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gallery Manifest Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and pin permanent digital manifests for art gallery exhibitions with immutable records. Discipline: Visual Art (exhibition cataloging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables reliable, permanent IPFS pinning of JSON manifests for galleries’ provenance needs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gallery Manifest Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-illustrator-portfolio-lock-3 Title: Illustrator Portfolio Lock Theme: Visual Art (visual-art) · portfolio archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently store and share your illustrator portfolios with verifiable IPFS links. Why Hedera: Pinata JWT simplifies secure uploading and pinning with permanent CID references. Market: TAM $1.5B — digital portfolio platforms | SAM $400M — illustrator-specific portfolio tools | SOM $80M — paid portfolio permanence services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Illustrator Portfolio Lock" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently store and share your illustrator portfolios with verifiable IPFS links. Discipline: Visual Art (portfolio archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT simplifies secure uploading and pinning with permanent CID references. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Illustrator Portfolio Lock" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-digital-canvas-ledger-4 Title: Digital Canvas Ledger Theme: Visual Art (visual-art) · painting provenance tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Record and pin painted artwork metadata to IPFS for permanent, tamper-proof provenance. Why Hedera: Pinata’s IPFS pinning ensures metadata immutability and easy retrieval for provenance. Market: TAM $10B — global art provenance solutions | SAM $2.7B — provenance platforms for painters | SOM $500M — subscription provenance verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Digital Canvas Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and pin painted artwork metadata to IPFS for permanent, tamper-proof provenance. Discipline: Visual Art (painting provenance tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning ensures metadata immutability and easy retrieval for provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Digital Canvas Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-nft-manifest-generator-5 Title: NFT Manifest Generator Theme: Visual Art (visual-art) · digital asset packaging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Bundle images and metadata into permanent IPFS manifests to guarantee NFT authenticity. Why Hedera: Pinata JWT provides seamless and permanent IPFS pinning ideal for NFT data integrity. Market: TAM $7B — NFT marketplace infrastructure | SAM $1.6B — NFT metadata management tools | SOM $350M — NFT authenticity services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Manifest Generator" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Bundle images and metadata into permanent IPFS manifests to guarantee NFT authenticity. Discipline: Visual Art (digital asset packaging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT provides seamless and permanent IPFS pinning ideal for NFT data integrity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Manifest Generator" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-interactive-art-pinning-6 Title: Interactive Art Pinning Theme: Visual Art (visual-art) · multimedia art preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin interactive art assets as JSON and media files, preserving experience forever on IPFS. Why Hedera: Pinata’s JWT upload supports complex JSON and media pinning for interaction fidelity. Market: TAM $800M — multimedia art platforms | SAM $200M — interactive digital art tools | SOM $45M — premium preservation subscriptions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Interactive Art Pinning" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin interactive art assets as JSON and media files, preserving experience forever on IPFS. Discipline: Visual Art (multimedia art preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s JWT upload supports complex JSON and media pinning for interaction fidelity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Interactive Art Pinning" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-exhibit-provenance-chain-7 Title: Exhibit Provenance Chain Theme: Visual Art (visual-art) · art exposition verification Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin exhibit documents and artwork proofs to guarantee authenticity for global exhibitions. Why Hedera: Pinata IPFS storage offers permanent, censorship-resistant verification of exhibit data. Market: TAM $4B — global exhibition services market | SAM $1B — digital exhibit verification tools | SOM $250M — paid provenance verification services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Exhibit Provenance Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin exhibit documents and artwork proofs to guarantee authenticity for global exhibitions. Discipline: Visual Art (art exposition verification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS storage offers permanent, censorship-resistant verification of exhibit data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Exhibit Provenance Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-painter-s-time-capsule-8 Title: Painter’s Time Capsule Theme: Visual Art (visual-art) · artistic process documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently archive each painting stage as images and notes pinned immutably on IPFS. Why Hedera: Pinata JWT enables secure sequential uploads to preserve process documentation forever. Market: TAM $600M — artist education and documentation | SAM $150M — process archiving platforms | SOM $30M — paid archival service usage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Painter’s Time Capsule" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently archive each painting stage as images and notes pinned immutably on IPFS. Discipline: Visual Art (artistic process documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT enables secure sequential uploads to preserve process documentation forever. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Painter’s Time Capsule" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-visual-art-supply-chain-9 Title: Visual Art Supply Chain Theme: Visual Art (visual-art) · art materials tracking Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin supplier certificates and provenance of materials to IPFS for buyer transparency. Why Hedera: Pinata’s permanent storage provides unalterable proof of authenticity for art supplies. Market: TAM $850M — art supplier certification market | SAM $210M — digital supply chain traceability | SOM $40M — transparent certification subscriptions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Visual Art Supply Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin supplier certificates and provenance of materials to IPFS for buyer transparency. Discipline: Visual Art (art materials tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s permanent storage provides unalterable proof of authenticity for art supplies. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Visual Art Supply Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-auto-manifest-builder-10 Title: Auto-Manifest Builder Theme: Visual Art (visual-art) · metadata automation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Automatically generate and pin complete IPFS manifests for artists’ works with minimal effort. Why Hedera: Pinata JWT API allows easy automated pins for bulk metadata and image uploads. Market: TAM $2B — creative automation software | SAM $500M — metadata management solutions | SOM $100M — paid automation feature adoption ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Auto-Manifest Builder" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automatically generate and pin complete IPFS manifests for artists’ works with minimal effort. Discipline: Visual Art (metadata automation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT API allows easy automated pins for bulk metadata and image uploads. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Auto-Manifest Builder" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-collaborative-art-archive-11 Title: Collaborative Art Archive Theme: Visual Art (visual-art) · group project documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin shared artworks and process files to IPFS for permanent collaborative history storage. Why Hedera: Pinata JWT supports multi-user authenticated uploads securing collaboration records. Market: TAM $1.5B — collaborative art tool market | SAM $350M — team project archiving platforms | SOM $75M — subscription for collaborative archiving ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Art Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin shared artworks and process files to IPFS for permanent collaborative history storage. Discipline: Visual Art (group project documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT supports multi-user authenticated uploads securing collaboration records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Art Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-style-transfer-keeper-12 Title: Style Transfer Keeper Theme: Visual Art (visual-art) · AI style preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin AI-generated style files and outputs permanently to safeguard unique artistic styles. Why Hedera: Pinata supports permanent pinning of JSON styles and image outputs ensuring longevity. Market: TAM $900M — AI art tools market | SAM $220M — style transfer application users | SOM $45M — paid style preservation services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Style Transfer Keeper" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin AI-generated style files and outputs permanently to safeguard unique artistic styles. Discipline: Visual Art (AI style preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata supports permanent pinning of JSON styles and image outputs ensuring longevity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Style Transfer Keeper" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-fair-digital-catalog-13 Title: Art Fair Digital Catalog Theme: Visual Art (visual-art) · event art listings Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create and pin permanent digital catalogs for art fairs accessible globally. Why Hedera: Pinata JWT pinning guarantees immutable, decentralized access to fair catalogs and data. Market: TAM $3B — global art event management | SAM $750M — digital catalog solutions | SOM $160M — event catalog subscription usage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Fair Digital Catalog" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and pin permanent digital catalogs for art fairs accessible globally. Discipline: Visual Art (event art listings). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT pinning guarantees immutable, decentralized access to fair catalogs and data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Fair Digital Catalog" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-generative-token-gallery-14 Title: Generative Token Gallery Theme: Visual Art (visual-art) · tokenized generative art Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin generative art manifests and images for permanent token-backed gallery display. Why Hedera: Pinata IPFS storage ensures consistent art data linked to blockchain tokens forever. Market: TAM $4B — tokenized art market | SAM $1B — token gallery platforms | SOM $210M — paid generative token services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Generative Token Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin generative art manifests and images for permanent token-backed gallery display. Discipline: Visual Art (tokenized generative art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS storage ensures consistent art data linked to blockchain tokens forever. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Generative Token Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-illustration-rights-ledger-15 Title: Illustration Rights Ledger Theme: Visual Art (visual-art) · digital rights management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin illustration licenses and usage terms permanently for transparent rights management. Why Hedera: Pinata’s immutable IPFS pins secure rights documents for easy verification. Market: TAM $1.8B — digital rights platforms | SAM $450M — illustration rights management | SOM $90M — subscription licensing verification ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Illustration Rights Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin illustration licenses and usage terms permanently for transparent rights management. Discipline: Visual Art (digital rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s immutable IPFS pins secure rights documents for easy verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Illustration Rights Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-augmented-art-archive-16 Title: Augmented Art Archive Theme: Visual Art (visual-art) · AR art content storage Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin AR textures, markers, and metadata permanently to IPFS for reliable augmented experiences. Why Hedera: Pinata supports permanent pinning of large AR asset files with consistent CID referencing. Market: TAM $2.5B — AR content creation market | SAM $600M — AR art publishing | SOM $130M — paid AR archival ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Augmented Art Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin AR textures, markers, and metadata permanently to IPFS for reliable augmented experiences. Discipline: Visual Art (AR art content storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata supports permanent pinning of large AR asset files with consistent CID referencing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Augmented Art Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-permanent-sketchbook-hub-17 Title: Permanent Sketchbook Hub Theme: Visual Art (visual-art) · digital sketch archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin your sketchbook pages as immutable IPFS entries to build a lifelong visual diary. Why Hedera: Pinata JWT allows easy permanent uploads ensuring sketches remain unchanged and accessible. Market: TAM $1B — digital artist journaling tools | SAM $270M — sketch archiving apps | SOM $60M — paid archival storage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Permanent Sketchbook Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin your sketchbook pages as immutable IPFS entries to build a lifelong visual diary. Discipline: Visual Art (digital sketch archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT allows easy permanent uploads ensuring sketches remain unchanged and accessible. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Permanent Sketchbook Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-visual-storyboard-chain-18 Title: Visual Storyboard Chain Theme: Visual Art (visual-art) · narrative art sequencing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin storyboards and frame metadata permanently for narrative visual art projects. Why Hedera: Pinata’s JSON and image pinning preserves sequence integrity on IPFS. Market: TAM $850M — visual storytelling tools | SAM $210M — storyboard digital platforms | SOM $45M — subscriptions for sequence permanence ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Visual Storyboard Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin storyboards and frame metadata permanently for narrative visual art projects. Discipline: Visual Art (narrative art sequencing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s JSON and image pinning preserves sequence integrity on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Visual Storyboard Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-curator-s-immutable-log-19 Title: Curator’s Immutable Log Theme: Visual Art (visual-art) · art exhibition curation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin curator notes, selections, and art metadata immutably to IPFS ensuring exhibition integrity. Why Hedera: Pinata JWT ensures curator data permanence and tamper resistance on-chain. Market: TAM $1.2B — digital curation software | SAM $320M — online exhibition tools | SOM $70M — curator service subscriptions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Curator’s Immutable Log" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin curator notes, selections, and art metadata immutably to IPFS ensuring exhibition integrity. Discipline: Visual Art (art exhibition curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT ensures curator data permanence and tamper resistance on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Curator’s Immutable Log" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-visual-remix-repository-20 Title: Visual Remix Repository Theme: Visual Art (visual-art) · art remix documentation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin remixed artworks and source attribution information permanently for remix transparency. Why Hedera: Pinata enables permanent IPFS storage linking source and new art metadata. Market: TAM $1.1B — remix art marketplaces | SAM $280M — remix attribution tools | SOM $55M — paid transparency services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Visual Remix Repository" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin remixed artworks and source attribution information permanently for remix transparency. Discipline: Visual Art (art remix documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables permanent IPFS storage linking source and new art metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Visual Remix Repository" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-unique-print-provenance-21 Title: Unique Print Provenance Theme: Visual Art (visual-art) · limited edition prints Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin limited print metadata and certificates immutably to prove authenticity for collectors. Why Hedera: Pinata IPFS pinning offers permanent certificate storage verified by CID. Market: TAM $6B — global print art market | SAM $1.5B — print provenance platforms | SOM $350M — certification and archival subscriptions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Unique Print Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin limited print metadata and certificates immutably to prove authenticity for collectors. Discipline: Visual Art (limited edition prints). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata IPFS pinning offers permanent certificate storage verified by CID. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Unique Print Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-artistic-collaboration-chain-22 Title: Artistic Collaboration Chain Theme: Visual Art (visual-art) · joint creation records Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin and permanently store co-created artworks and contribution metadata for fair crediting. Why Hedera: Pinata JWT multi-upload supports joint data pinning with immutable records. Market: TAM $1.4B — collaborative art tools | SAM $360M — joint creative project platforms | SOM $75M — paid collaboration archiving ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Artistic Collaboration Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin and permanently store co-created artworks and contribution metadata for fair crediting. Discipline: Visual Art (joint creation records). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT multi-upload supports joint data pinning with immutable records. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Artistic Collaboration Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-visual-rights-archive-23 Title: Visual Rights Archive Theme: Visual Art (visual-art) · copyright preservation Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin copyright registration data and proofs permanently for artists’ legal protection. Why Hedera: Pinata’s permanent IPFS pinning guarantees copyright data immutability and accessibility. Market: TAM $3B — copyright registration market | SAM $800M — digital copyright services | SOM $180M — subscription copyright storage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Visual Rights Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin copyright registration data and proofs permanently for artists’ legal protection. Discipline: Visual Art (copyright preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s permanent IPFS pinning guarantees copyright data immutability and accessibility. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Visual Rights Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-dynamic-exhibit-snapshot-24 Title: Dynamic Exhibit Snapshot Theme: Visual Art (visual-art) · exhibit state recording Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin snapshots of changing exhibitions and arrangements permanently for historical records. Why Hedera: Pinata JWT allows frequent permanent pins of evolving JSON/image data on IPFS. Market: TAM $1B — exhibit documentation solutions | SAM $260M — art show documentation tools | SOM $55M — paid exhibit snapshot services ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dynamic Exhibit Snapshot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin snapshots of changing exhibitions and arrangements permanently for historical records. Discipline: Visual Art (exhibit state recording). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT allows frequent permanent pins of evolving JSON/image data on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dynamic Exhibit Snapshot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-canvas-collaborate-0 Title: Canvas Collaborate Theme: Visual Art (visual-art) · collaborative painting Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable painters to co-create artworks with seamless onchain identity and gasless contributions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allow smooth collaboration without gas friction or complex wallets. Market: TAM $8B — global collaborative art platforms | SAM $2B — digital collaboration tools for painters | SOM $100M — early adopters in gasless art collaboration ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Canvas Collaborate" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable painters to co-create artworks with seamless onchain identity and gasless contributions. Discipline: Visual Art (collaborative painting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allow smooth collaboration without gas friction or complex wallets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Canvas Collaborate" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-illustrator-guild-1 Title: Illustrator Guild Theme: Visual Art (visual-art) · illustrator community Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create a hosted network for illustrators to share, sell, and verify work with easy login and zero gas fees. Why Hedera: Magic Link email sign-in removes blockchain barriers for illustrators expanding their market. Market: TAM $4B — global illustration market | SAM $1B — digital sales of illustrations | SOM $50M — networks enabling gasless art transactions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Illustrator Guild" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create a hosted network for illustrators to share, sell, and verify work with easy login and zero gas fees. Discipline: Visual Art (illustrator community). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in removes blockchain barriers for illustrators expanding their market. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Illustrator Guild" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-generative-canvas-2 Title: Generative Canvas Theme: Visual Art (visual-art) · generative art Hedera hook: Magic Link email wallet [wallet UX] Pitch: Let generative artists deploy and monetize code-driven art with instant gasless onboarding and transactions. Why Hedera: Hedera's fixed sub-cent fees plus embedded wallet offer frictionless deployment and sales for generative artists. Market: TAM $3B — generative digital art sales | SAM $800M — code-based art platforms | SOM $40M — gasless onboarding for generative artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Generative Canvas" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Let generative artists deploy and monetize code-driven art with instant gasless onboarding and transactions. Discipline: Visual Art (generative art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees plus embedded wallet offer frictionless deployment and sales for generative artists. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Generative Canvas" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-gallery-ledger-3 Title: Gallery Ledger Theme: Visual Art (visual-art) · art provenance tracking Hedera hook: Magic Link email wallet [wallet UX] Pitch: Provide gallerists with gasless, onchain tools to prove artwork provenance and authenticity effortlessly. Why Hedera: the embedded wallet-enabled social login and Hedera's fixed sub-cent fees simplify provenance updates without gas costs. Market: TAM $15B — global art provenance solutions | SAM $4B — provenance tools for galleries | SOM $200M — gasless provenance management users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gallery Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Provide gallerists with gasless, onchain tools to prove artwork provenance and authenticity effortlessly. Discipline: Visual Art (art provenance tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet-enabled social login and Hedera's fixed sub-cent fees simplify provenance updates without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gallery Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-palette-swap-4 Title: Palette Swap Theme: Visual Art (visual-art) · color exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Allow painters and illustrators to exchange and license color palettes with transparent, gasless transactions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable easy palette licensing with zero gas hurdles. Market: TAM $500M — digital color licensing market | SAM $120M — palette trading platforms | SOM $10M — early palette exchange adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Palette Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Allow painters and illustrators to exchange and license color palettes with transparent, gasless transactions. Discipline: Visual Art (color exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable easy palette licensing with zero gas hurdles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Palette Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-sketchstream-5 Title: SketchStream Theme: Visual Art (visual-art) · live drawing streams Hedera hook: Magic Link email wallet [wallet UX] Pitch: Stream live sketches with integrated onchain tips and gasless wallet logins for artist-audience engagement. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees allow instant, gas-free microtransactions during live art streams. Market: TAM $1.5B — live streaming art markets | SAM $400M — live tip-enabled drawing platforms | SOM $25M — gasless tipping users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SketchStream" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Stream live sketches with integrated onchain tips and gasless wallet logins for artist-audience engagement. Discipline: Visual Art (live drawing streams). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees allow instant, gas-free microtransactions during live art streams. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SketchStream" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-nft-splitter-6 Title: NFT Splitter Theme: Visual Art (visual-art) · fractional art ownership Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable painters and collectors to split artwork ownership onchain with easy social login and no gas fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees make fractional ownership seamless without transaction costs. Market: TAM $10B — art fractionalization market | SAM $3B — digital sharing of artwork equity | SOM $150M — gasless split ownership users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Splitter" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable painters and collectors to split artwork ownership onchain with easy social login and no gas fees. Discipline: Visual Art (fractional art ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees make fractional ownership seamless without transaction costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Splitter" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-artswap-social-7 Title: ArtSwap Social Theme: Visual Art (visual-art) · art barter communities Hedera hook: Magic Link email wallet [wallet UX] Pitch: Build a social marketplace for illustrators to swap artworks and services without gas concerns. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees facilitate frictionless, gasless peer-to-peer swaps. Market: TAM $1B — global art barter economy | SAM $350M — illustrator barter platforms | SOM $20M — gasless transaction swap users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ArtSwap Social" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Build a social marketplace for illustrators to swap artworks and services without gas concerns. Discipline: Visual Art (art barter communities). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees facilitate frictionless, gasless peer-to-peer swaps. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ArtSwap Social" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-sketchvault-8 Title: SketchVault Theme: Visual Art (visual-art) · secure sketch storage Hedera hook: Magic Link email wallet [wallet UX] Pitch: Offer painters a gasless, onchain-secured vault for storing and sharing preliminary sketches with social login. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable secure, cost-free storage of sensitive creative drafts. Market: TAM $700M — secure digital art storage | SAM $200M — sketch archival platforms | SOM $12M — gasless storage adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SketchVault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Offer painters a gasless, onchain-secured vault for storing and sharing preliminary sketches with social login. Discipline: Visual Art (secure sketch storage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable secure, cost-free storage of sensitive creative drafts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SketchVault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-brushstroke-rights-9 Title: Brushstroke Rights Theme: Visual Art (visual-art) · digital rights management Hedera hook: Magic Link email wallet [wallet UX] Pitch: Help illustrators manage and transfer licenses onchain with simple Google sign-in and zero gas. Why Hedera: the embedded wallet primitive ensures easy rights transfers without user knowledge of gas or keys. Market: TAM $5B — digital art licensing market | SAM $1.5B — licensing platforms for illustrators | SOM $80M — gasless DRM users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Brushstroke Rights" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Help illustrators manage and transfer licenses onchain with simple Google sign-in and zero gas. Discipline: Visual Art (digital rights management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet primitive ensures easy rights transfers without user knowledge of gas or keys. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Brushstroke Rights" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-artchain-auctions-10 Title: ArtChain Auctions Theme: Visual Art (visual-art) · auction facilitation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create gasless onchain auctions for visual artists with fast wallet onboarding and sponsored bids. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees prevent bidding friction from gas fees. Market: TAM $12B — global art auctions | SAM $3.5B — digital art auction platforms | SOM $180M — gasless auction users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ArtChain Auctions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create gasless onchain auctions for visual artists with fast wallet onboarding and sponsored bids. Discipline: Visual Art (auction facilitation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees prevent bidding friction from gas fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ArtChain Auctions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-framefi-marketplace-11 Title: FrameFi Marketplace Theme: Visual Art (visual-art) · digital framing services Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable illustrators to sell artworks with embedded gasless framing add-ons and easy wallet login. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees streamline gasless purchase of framing options. Market: TAM $600M — digital art framing market | SAM $150M — framing add-on buyers | SOM $10M — gasless framing service users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameFi Marketplace" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable illustrators to sell artworks with embedded gasless framing add-ons and easy wallet login. Discipline: Visual Art (digital framing services). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees streamline gasless purchase of framing options. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameFi Marketplace" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-mural-mesh-12 Title: Mural Mesh Theme: Visual Art (visual-art) · public mural collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Coordinate large-scale public murals with painters via gasless onchain collaboration and social login. Why Hedera: the embedded wallet Hedera's fixed sub-cent fees ease multi-party approval without gas costs. Market: TAM $1B — public mural funding | SAM $300M — collaborative mural initiatives | SOM $18M — gasless onchain mural participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Mural Mesh" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Coordinate large-scale public murals with painters via gasless onchain collaboration and social login. Discipline: Visual Art (public mural collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet Hedera's fixed sub-cent fees ease multi-party approval without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Mural Mesh" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-colorstory-13 Title: ColorStory Theme: Visual Art (visual-art) · color narrative art Hedera hook: Magic Link email wallet [wallet UX] Pitch: Illustrators share evolving color-based stories secured onchain with gasless updates and Google sign-in. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees allow smooth story versioning without transaction costs. Market: TAM $350M — digital narrative art market | SAM $90M — color story platforms | SOM $7M — gasless narrative update users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ColorStory" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Illustrators share evolving color-based stories secured onchain with gasless updates and Google sign-in. Discipline: Visual Art (color narrative art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees allow smooth story versioning without transaction costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ColorStory" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-token-tapestry-14 Title: Token Tapestry Theme: Visual Art (visual-art) · textile pattern art Hedera hook: Magic Link email wallet [wallet UX] Pitch: Artists tokenize and sell unique textile patterns with gasless wallet integration and Hedera's fixed sub-cent fees. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees ensure seamless gasless sales of tokenized patterns. Market: TAM $2B — digital textile art market | SAM $500M — pattern tokenization | SOM $30M — gasless textile pattern buyers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Token Tapestry" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Artists tokenize and sell unique textile patterns with gasless wallet integration and Hedera's fixed sub-cent fees. Discipline: Visual Art (textile pattern art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees ensure seamless gasless sales of tokenized patterns. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Token Tapestry" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-gallery-ghost-15 Title: Gallery Ghost Theme: Visual Art (visual-art) · virtual gallery hosting Hedera hook: Magic Link email wallet [wallet UX] Pitch: Gallerists create virtual galleries with gasless onchain ticketing and Google-based wallet onboarding. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable cost-free event participation. Market: TAM $5B — virtual gallery ticket sales | SAM $1.2B — digital gallery hosting platforms | SOM $70M — gasless ticket user base ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gallery Ghost" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Gallerists create virtual galleries with gasless onchain ticketing and Google-based wallet onboarding. Discipline: Visual Art (virtual gallery hosting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable cost-free event participation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gallery Ghost" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-brushbot-16 Title: BrushBot Theme: Visual Art (visual-art) · AI-assisted painting Hedera hook: Magic Link email wallet [wallet UX] Pitch: Integrate AI painting tools powered by gasless onchain user verification and transactions. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees provide smooth tool access without blockchain complexity. Market: TAM $3.5B — AI art tool market | SAM $900M — AI painter subscriptions | SOM $45M — gasless AI tool users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "BrushBot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Integrate AI painting tools powered by gasless onchain user verification and transactions. Discipline: Visual Art (AI-assisted painting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees provide smooth tool access without blockchain complexity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "BrushBot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-sketchset-swap-17 Title: SketchSet Swap Theme: Visual Art (visual-art) · brush preset exchange Hedera hook: Magic Link email wallet [wallet UX] Pitch: Illustrators trade and license brush presets with zero gas fees and easy Google sign-in wallets. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees eliminate friction in preset licensing. Market: TAM $400M — digital brush market | SAM $100M — brush preset trading | SOM $8M — gasless brush exchange users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SketchSet Swap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Illustrators trade and license brush presets with zero gas fees and easy Google sign-in wallets. Discipline: Visual Art (brush preset exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees eliminate friction in preset licensing. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SketchSet Swap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-artchain-critique-18 Title: ArtChain Critique Theme: Visual Art (visual-art) · peer review platform Hedera hook: Magic Link email wallet [wallet UX] Pitch: Painters share work for peer feedback with onchain reputation and gasless login/transactions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable frictionless participation and reputation building. Market: TAM $700M — digital art critique services | SAM $180M — peer review platforms for artists | SOM $12M — gasless critique users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ArtChain Critique" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Painters share work for peer feedback with onchain reputation and gasless login/transactions. Discipline: Visual Art (peer review platform). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees enable frictionless participation and reputation building. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ArtChain Critique" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-illustrate-impact-19 Title: Illustrate Impact Theme: Visual Art (visual-art) · art philanthropy Hedera hook: Magic Link email wallet [wallet UX] Pitch: Facilitate gasless donations and sponsorships for illustrators supporting social causes via easy login. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure smooth, zero-fee charitable transactions. Market: TAM $2.5B — art philanthropy market | SAM $600M — donations to illustrators | SOM $35M — gasless charity transaction users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Illustrate Impact" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate gasless donations and sponsorships for illustrators supporting social causes via easy login. Discipline: Visual Art (art philanthropy). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees ensure smooth, zero-fee charitable transactions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Illustrate Impact" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-colorchain-auctions-20 Title: Colorchain Auctions Theme: Visual Art (visual-art) · color-themed art auctions Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host auctions of color-driven artworks with gasless bidding and quick wallet onboarding. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees remove gas fee barriers in niche auctions. Market: TAM $1B — color art auction market | SAM $250M — color themed auction platforms | SOM $15M — gasless bidders ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Colorchain Auctions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host auctions of color-driven artworks with gasless bidding and quick wallet onboarding. Discipline: Visual Art (color-themed art auctions). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees remove gas fee barriers in niche auctions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Colorchain Auctions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-illustrator-ink-21 Title: Illustrator Ink Theme: Visual Art (visual-art) · digital ink art Hedera hook: Magic Link email wallet [wallet UX] Pitch: Support ink artists selling limited editions with gasless minting and Hedera's fixed sub-cent fees flows. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees create seamless gasless mint experiences. Market: TAM $1.5B — digital ink art market | SAM $400M — limited edition ink artworks | SOM $22M — gasless minting users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Illustrator Ink" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Support ink artists selling limited editions with gasless minting and Hedera's fixed sub-cent fees flows. Discipline: Visual Art (digital ink art). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees create seamless gasless mint experiences. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Illustrator Ink" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-generative-gallery-22 Title: Generative Gallery Theme: Visual Art (visual-art) · algorithmic art exhibition Hedera hook: Magic Link email wallet [wallet UX] Pitch: Curate generative art exhibits featuring gasless ticketing and social login onboarding. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees facilitate easy event access without fees. Market: TAM $1.8B — digital generative art events | SAM $500M — ticketing for generative exhibits | SOM $28M — gasless event participants ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Generative Gallery" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Curate generative art exhibits featuring gasless ticketing and social login onboarding. Discipline: Visual Art (algorithmic art exhibition). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees facilitate easy event access without fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Generative Gallery" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-artswap-network-23 Title: ArtSwap Network Theme: Visual Art (visual-art) · visual art trades Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable painters and illustrators to swap artworks onchain with gasless wallet bootstrapped by social login. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees guarantee frictionless, cost-free art trades. Market: TAM $2B — global art swap market | SAM $550M — digital artwork trade platforms | SOM $33M — gasless swap users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ArtSwap Network" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable painters and illustrators to swap artworks onchain with gasless wallet bootstrapped by social login. Discipline: Visual Art (visual art trades). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees guarantee frictionless, cost-free art trades. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ArtSwap Network" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-canvas-chronicles-0 Title: Canvas Chronicles Theme: Visual Art (visual-art) · painting portfolios Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and track authentic painting portfolios with verifiable creation history. Why Hedera: NFT provenance mint ensures immutable creator ownership and art authenticity on-chain. Market: TAM $65B — global visual art market | SAM $2.5B — online artist portfolio services | SOM $50M — NFT-based artist portfolio adopters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Canvas Chronicles" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and track authentic painting portfolios with verifiable creation history. Discipline: Visual Art (painting portfolios). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mint ensures immutable creator ownership and art authenticity on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Canvas Chronicles" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-sketch-stamp-1 Title: Sketch Stamp Theme: Visual Art (visual-art) · illustration drafts Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate and timestamp early illustration drafts to prove originality and creative process. Why Hedera: HTS NFT mint links creators to IPFS-stored drafts ensuring provenance auditability. Market: TAM $65B — global visual art market | SAM $800M — digital illustration sector | SOM $10M — provenance-tracked illustration sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sketch Stamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate and timestamp early illustration drafts to prove originality and creative process. Discipline: Visual Art (illustration drafts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT mint links creators to IPFS-stored drafts ensuring provenance auditability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sketch Stamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-generative-genesis-2 Title: Generative Genesis Theme: Visual Art (visual-art) · generative art iterations Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track and mint each generative art iteration as unique collectible tokens. Why Hedera: Onchain NFTs securely link generative code outputs with IPFS CIDs for true provenance. Market: TAM $65B — global visual art market | SAM $400M — generative art niche | SOM $5M — NFT generative art collectors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Generative Genesis" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and mint each generative art iteration as unique collectible tokens. Discipline: Visual Art (generative art iterations). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFTs securely link generative code outputs with IPFS CIDs for true provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Generative Genesis" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-gallery-gatekeeper-3 Title: Gallery Gatekeeper Theme: Visual Art (visual-art) · gallery asset curation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Enable galleries to mint and verify provenance of physical artwork displayed or sold. Why Hedera: HTS NFT tokens provide immutable proof of artwork origin and gallery authentication. Market: TAM $65B — global visual art market | SAM $7B — global gallery sales | SOM $100M — digital provenance certified artworks ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gallery Gatekeeper" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable galleries to mint and verify provenance of physical artwork displayed or sold. Discipline: Visual Art (gallery asset curation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide immutable proof of artwork origin and gallery authentication. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gallery Gatekeeper" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-color-ledger-4 Title: Color Ledger Theme: Visual Art (visual-art) · color palette preservation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint unique NFTs capturing and securing artist-specific color palettes and schemes. Why Hedera: NFT provenance mint anchors palette data immutably linked to the creator’s token. Market: TAM $65B — global visual art market | SAM $300M — digital art toolsets | SOM $4M — color-palette licensing via NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Color Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint unique NFTs capturing and securing artist-specific color palettes and schemes. Discipline: Visual Art (color palette preservation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mint anchors palette data immutably linked to the creator’s token. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Color Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-brushstroke-chain-5 Title: Brushstroke Chain Theme: Visual Art (visual-art) · brush technique archives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Record and mint signature brushstroke styles as NFTs for artist branding and licensing. Why Hedera: HTS NFT tokens verify authentic brushstroke styles with permanent on-chain IPFS proof. Market: TAM $65B — global visual art market | SAM $150M — visual art teaching content | SOM $3M — brushstroke NFT sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Brushstroke Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Record and mint signature brushstroke styles as NFTs for artist branding and licensing. Discipline: Visual Art (brush technique archives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens verify authentic brushstroke styles with permanent on-chain IPFS proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Brushstroke Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-minted-murals-6 Title: Minted Murals Theme: Visual Art (visual-art) · public mural documentation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Authenticate and immortalize public murals with creator-owned NFT provenance tokens. Why Hedera: Immutable onchain minting guarantees provenance and long-term mural authenticity. Market: TAM $65B — global visual art market | SAM $500M — public art projects | SOM $8M — NFT mural provenance fees ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Minted Murals" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Authenticate and immortalize public murals with creator-owned NFT provenance tokens. Discipline: Visual Art (public mural documentation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable onchain minting guarantees provenance and long-term mural authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Minted Murals" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-framechain-7 Title: FrameChain Theme: Visual Art (visual-art) · art reproduction control Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Control and verify reproduction rights of artworks via mintable provenance NFTs. Why Hedera: HTS NFT provenance tokens enable robust reproduction right tracking onchain. Market: TAM $65B — global visual art market | SAM $3B — art reproduction licensing | SOM $50M — provenance NFT reproduction licenses ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FrameChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Control and verify reproduction rights of artworks via mintable provenance NFTs. Discipline: Visual Art (art reproduction control). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT provenance tokens enable robust reproduction right tracking onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FrameChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-illustrator-s-imprint-8 Title: Illustrator’s Imprint Theme: Visual Art (visual-art) · signed digital prints Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint signed digital prints with verifiable creator provenance directly on the blockchain. Why Hedera: HTS NFT tokens link signed print IPFS CIDs to creator ownership proofs. Market: TAM $65B — global visual art market | SAM $1B — digital print sales | SOM $15M — NFT print provenance market ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Illustrator’s Imprint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint signed digital prints with verifiable creator provenance directly on the blockchain. Discipline: Visual Art (signed digital prints). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link signed print IPFS CIDs to creator ownership proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Illustrator’s Imprint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-provenance-palette-9 Title: Provenance Palette Theme: Visual Art (visual-art) · artist palette provenance Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Digitally certify and mint provenance tokens for unique physical artist palettes. Why Hedera: NFT minting onchain ensures unalterable provenance linked to physical artifacts. Market: TAM $65B — global visual art market | SAM $50M — artist tools & collectibles | SOM $1.5M — NFT-verified palette auctions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Provenance Palette" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitally certify and mint provenance tokens for unique physical artist palettes. Discipline: Visual Art (artist palette provenance). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting onchain ensures unalterable provenance linked to physical artifacts. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Provenance Palette" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-pixel-provenance-10 Title: Pixel Provenance Theme: Visual Art (visual-art) · pixel art authentication Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint pixel art pieces with immutable creator provenance and IPFS metadata. Why Hedera: HTS NFT on Hedera testnet anchors pixel art provenance for collectors and artists alike. Market: TAM $65B — global visual art market | SAM $250M — pixel art market | SOM $5M — NFT pixel art provenance ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Pixel Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint pixel art pieces with immutable creator provenance and IPFS metadata. Discipline: Visual Art (pixel art authentication). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT on Hedera testnet anchors pixel art provenance for collectors and artists alike. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Pixel Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-exhibit-echo-11 Title: Exhibit Echo Theme: Visual Art (visual-art) · virtual exhibition records Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Preserve virtual gallery exhibits with creator-owned NFT provenance linked to IPFS. Why Hedera: Immutable NFT minting captures and certifies virtual exhibit provenance onchain. Market: TAM $65B — global visual art market | SAM $1B — virtual exhibition revenues | SOM $12M — NFT exhibit provenance buyers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Exhibit Echo" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Preserve virtual gallery exhibits with creator-owned NFT provenance linked to IPFS. Discipline: Visual Art (virtual exhibition records). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFT minting captures and certifies virtual exhibit provenance onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Exhibit Echo" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-storyboard-stamp-12 Title: Storyboard Stamp Theme: Visual Art (visual-art) · concept art progression Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint sequential NFTs to authenticate evolving concept art storyboards and ideas. Why Hedera: HTS NFT tokens create immutable chains of concept art evolution on the blockchain. Market: TAM $65B — global visual art market | SAM $350M — concept art services | SOM $6M — provenance NFT storyboards ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Storyboard Stamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint sequential NFTs to authenticate evolving concept art storyboards and ideas. Discipline: Visual Art (concept art progression). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens create immutable chains of concept art evolution on the blockchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Storyboard Stamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-masterstroke-mint-13 Title: Masterstroke Mint Theme: Visual Art (visual-art) · signature artwork certs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create immutable certificates of authenticity for signature artworks via NFT provenance mint. Why Hedera: NFT provenance ensures unforgeable certification linked to IPFS-stored artwork data. Market: TAM $65B — global visual art market | SAM $9B — art certification services | SOM $100M — NFT-based authenticity certificates ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Masterstroke Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create immutable certificates of authenticity for signature artworks via NFT provenance mint. Discipline: Visual Art (signature artwork certs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures unforgeable certification linked to IPFS-stored artwork data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Masterstroke Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-layer-ledger-14 Title: Layer Ledger Theme: Visual Art (visual-art) · digital painting layers Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Track and mint individual painting layers proving creative process and ownership. Why Hedera: HTS NFT minting uniquely links layered IPFS CIDs with artist ownership proofs. Market: TAM $65B — global visual art market | SAM $400M — digital art creation tools | SOM $7M — provenance NFTs of layers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Layer Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and mint individual painting layers proving creative process and ownership. Discipline: Visual Art (digital painting layers). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting uniquely links layered IPFS CIDs with artist ownership proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Layer Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-artchain-auction-15 Title: Artchain Auction Theme: Visual Art (visual-art) · secondary art sales Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Integrate provenance minting into art auctions to verify authenticity and creator royalties. Why Hedera: Onchain NFT provenance mints enable transparent ownership and royalty enforcement. Market: TAM $65B — global visual art market | SAM $15B — secondary art market | SOM $300M — NFT provenance auction fees ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Artchain Auction" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Integrate provenance minting into art auctions to verify authenticity and creator royalties. Discipline: Visual Art (secondary art sales). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain NFT provenance mints enable transparent ownership and royalty enforcement. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Artchain Auction" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-color-code-vault-16 Title: Color Code Vault Theme: Visual Art (visual-art) · digital color codes Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely store and prove origin of proprietary digital color codes with mintable NFTs. Why Hedera: HTS NFT provenance mint guarantees creator ownership and IPFS hash immutability. Market: TAM $65B — global visual art market | SAM $200M — digital color tool market | SOM $3M — NFT color code licenses ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Color Code Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store and prove origin of proprietary digital color codes with mintable NFTs. Discipline: Visual Art (digital color codes). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT provenance mint guarantees creator ownership and IPFS hash immutability. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Color Code Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-mentor-mark-17 Title: Art Mentor Mark Theme: Visual Art (visual-art) · art mentorship proof Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint NFTs to certify mentorship lineage and creative influence among artists. Why Hedera: HTS NFT tokens create verifiable mentorship provenance anchored on the blockchain. Market: TAM $65B — global visual art market | SAM $500M — art education platforms | SOM $8M — NFT mentorship certificates ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Mentor Mark" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint NFTs to certify mentorship lineage and creative influence among artists. Discipline: Visual Art (art mentorship proof). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens create verifiable mentorship provenance anchored on the blockchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Mentor Mark" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-signature-seal-18 Title: Signature Seal Theme: Visual Art (visual-art) · digital signature minting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Allow artists to mint unique digital signatures as NFT proof of authorship and authenticity. Why Hedera: Immutable NFT mint links digital signature to IPFS asset verifiably onchain. Market: TAM $65B — global visual art market | SAM $1B — digital signature services | SOM $12M — NFT signature deployments ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Signature Seal" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Allow artists to mint unique digital signatures as NFT proof of authorship and authenticity. Discipline: Visual Art (digital signature minting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFT mint links digital signature to IPFS asset verifiably onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Signature Seal" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-nft-mirror-19 Title: NFT Mirror Theme: Visual Art (visual-art) · artistic reflection sets Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint paired NFTs representing original and reflected artworks to showcase creative duality. Why Hedera: HTS NFT tokens prove paired provenance anchored to immutable IPFS data sets. Market: TAM $65B — global visual art market | SAM $250M — contemporary art niche | SOM $4M — NFT paired artwork sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Mirror" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint paired NFTs representing original and reflected artworks to showcase creative duality. Discipline: Visual Art (artistic reflection sets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens prove paired provenance anchored to immutable IPFS data sets. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Mirror" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-art-rewind-20 Title: Art Rewind Theme: Visual Art (visual-art) · creative process playback Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint stepwise NFTs documenting artwork creation stages for replay and provenance. Why Hedera: HTS NFT minting onchain links IPFS CIDs capturing each creative moment forever. Market: TAM $65B — global visual art market | SAM $350M — digital art tutorials | SOM $6M — NFT creative process records ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Art Rewind" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint stepwise NFTs documenting artwork creation stages for replay and provenance. Discipline: Visual Art (creative process playback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT minting onchain links IPFS CIDs capturing each creative moment forever. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Art Rewind" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-gallery-provenance-21 Title: Gallery Provenance Theme: Visual Art (visual-art) · physical gallery tracking Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Digitally certify artwork origin and gallery exhibition history onchain with NFTs. Why Hedera: NFT provenance mint ensures transparent gallery provenance authenticated on Hedera testnet. Market: TAM $65B — global visual art market | SAM $6B — gallery exhibited artwork | SOM $90M — NFT gallery provenance usage ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Gallery Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitally certify artwork origin and gallery exhibition history onchain with NFTs. Discipline: Visual Art (physical gallery tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance mint ensures transparent gallery provenance authenticated on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Gallery Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-nft-colorgram-22 Title: NFT Colorgram Theme: Visual Art (visual-art) · chromatic art NFTs Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create NFTs representing exclusive color-themed artworks with immutable provenance. Why Hedera: HTS NFT mint links color-centric art stored on IPFS with verified creator proof. Market: TAM $65B — global visual art market | SAM $500M — color-focused art collectors | SOM $7M — NFT color art trading ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NFT Colorgram" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs representing exclusive color-themed artworks with immutable provenance. Discipline: Visual Art (chromatic art NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT mint links color-centric art stored on IPFS with verified creator proof. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NFT Colorgram" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-motion-mint-23 Title: Motion Mint Theme: Visual Art (visual-art) · animated illustration ownership Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint creator-owned NFTs for animated illustrations backed by IPFS-stored assets. Why Hedera: HTS NFT provenance mint guarantees creator control and provenance of animation files. Market: TAM $65B — global visual art market | SAM $600M — animated illustration market | SOM $9M — NFT animated art sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Motion Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint creator-owned NFTs for animated illustrations backed by IPFS-stored assets. Discipline: Visual Art (animated illustration ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT provenance mint guarantees creator control and provenance of animation files. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Motion Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA visual-art-provenance-palette-24 Title: Provenance Palette Theme: Visual Art (visual-art) · physical palette digitization Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Digitize and mint physical palette images as NFTs preserving unique artist color mixes. Why Hedera: Immutable NFT mint authenticates physical palette provenance via IPFS hashes. Market: TAM $65B — global visual art market | SAM $50M — art collector memorabilia | SOM $1.2M — NFT palette collector sales ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Provenance Palette" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Digitize and mint physical palette images as NFTs preserving unique artist color mixes. Discipline: Visual Art (physical palette digitization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable NFT mint authenticates physical palette provenance via IPFS hashes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Provenance Palette" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ============================================================================== THEME · Writing, Poetry & Narrative writers, poets, screenwriters, narrative designers ============================================================================== ------------------------------------------------------------------------------ IDEA writing-verseledger-0 Title: VerseLedger Theme: Writing, Poetry & Narrative (writing) · poetry archiving Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Securely timestamp and prove original poem authorship on the blockchain to prevent plagiarism. Why Hedera: Immutable contract records create verifiable proof of original literary creation dates. Market: TAM $1.5B — global writing tools market | SAM $300M — digital poetry platforms | SOM $25M — blockchain-based literary provenance tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseLedger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely timestamp and prove original poem authorship on the blockchain to prevent plagiarism. Discipline: Writing, Poetry & Narrative (poetry archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable contract records create verifiable proof of original literary creation dates. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseLedger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrativevote-1 Title: NarrativeVote Theme: Writing, Poetry & Narrative (writing) · interactive storytelling Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable readers to vote on story plot directions through blockchain-based decision contracts. Why Hedera: Smart contracts transparently tally reader choices for decentralized story progression. Market: TAM $1.5B — writing tools market | SAM $200M — choose-your-own-adventure digital narratives | SOM $15M — blockchain interactive fiction voting ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrativeVote" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable readers to vote on story plot directions through blockchain-based decision contracts. Discipline: Writing, Poetry & Narrative (interactive storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts transparently tally reader choices for decentralized story progression. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrativeVote" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptroyalty-2 Title: ScriptRoyalty Theme: Writing, Poetry & Narrative (writing) · screenwriting rights Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Automate royalty distribution for collaborative screenplays using verified smart contract agreements. Why Hedera: Onchain contracts enforce transparent and automatic profit sharing among co-writers. Market: TAM $1.5B — writing and screenwriting tools | SAM $400M — digital script collaboration | SOM $30M — blockchain-enabled script rights management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptRoyalty" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Automate royalty distribution for collaborative screenplays using verified smart contract agreements. Discipline: Writing, Poetry & Narrative (screenwriting rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain contracts enforce transparent and automatic profit sharing among co-writers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptRoyalty" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetchain-3 Title: PoetChain Theme: Writing, Poetry & Narrative (writing) · collaborative poetry Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Facilitate co-authored poems with edit histories securely stored on blockchain for trust. Why Hedera: Hedera testnet smart contracts maintain immutable collaboration logs to verify contributions. Market: TAM $1.5B — writing platforms globally | SAM $150M — collaborative poetry communities | SOM $12M — blockchain-based co-author tracking ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoetChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Facilitate co-authored poems with edit histories securely stored on blockchain for trust. Discipline: Writing, Poetry & Narrative (collaborative poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts maintain immutable collaboration logs to verify contributions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoetChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plotproof-4 Title: PlotProof Theme: Writing, Poetry & Narrative (writing) · story validation Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Timestamp story drafts to prove originality and track narrative evolution over time. Why Hedera: Immutable timestamps on Hedera testnet verify manuscript creation moments reliably and publicly. Market: TAM $1.5B — global writing market | SAM $500M — self-publishing tools | SOM $40M — onchain literary authentication ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotProof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Timestamp story drafts to prove originality and track narrative evolution over time. Discipline: Writing, Poetry & Narrative (story validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable timestamps on Hedera testnet verify manuscript creation moments reliably and publicly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotProof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-verseauction-5 Title: VerseAuction Theme: Writing, Poetry & Narrative (writing) · poetry marketplace Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Sell original poems as verified digital assets with ownership recorded on Hedera testnet. Why Hedera: Smart contracts enable secure, transparent poem sales and ownership transfers. Market: TAM $1.5B — writing and publishing tools | SAM $250M — digital art and writing marketplaces | SOM $18M — blockchain-based poem sales platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseAuction" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Sell original poems as verified digital assets with ownership recorded on Hedera testnet. Discipline: Writing, Poetry & Narrative (poetry marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts enable secure, transparent poem sales and ownership transfers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseAuction" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narratordao-6 Title: NarratorDAO Theme: Writing, Poetry & Narrative (writing) · community storytelling Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create decentralized stories collectively owned and governed by contributors via DAO smart contracts. Why Hedera: Hedera testnet contracts enforce democratic participation and profit sharing in story creation. Market: TAM $1.5B — writing tools market | SAM $180M — community storytelling apps | SOM $14M — blockchain-based narrative DAOs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarratorDAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create decentralized stories collectively owned and governed by contributors via DAO smart contracts. Discipline: Writing, Poetry & Narrative (community storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enforce democratic participation and profit sharing in story creation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarratorDAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scenemint-7 Title: SceneMint Theme: Writing, Poetry & Narrative (writing) · script segments Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint individual screenplay scenes as NFTs to license or sell specific script parts securely. Why Hedera: Smart contracts verify uniqueness and ownership of discrete narrative elements. Market: TAM $1.5B — screenwriting tools | SAM $350M — digital script licensing | SOM $25M — NFT-based script segment trading ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "SceneMint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint individual screenplay scenes as NFTs to license or sell specific script parts securely. Discipline: Writing, Poetry & Narrative (script segments). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts verify uniqueness and ownership of discrete narrative elements. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "SceneMint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-metapoem-8 Title: MetaPoem Theme: Writing, Poetry & Narrative (writing) · dynamic poetry Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create poems that evolve via blockchain triggers based on reader interactions or events. Why Hedera: Hedera testnet contracts enable programmable, immutable poem state changes. Market: TAM $1.5B — writing creativity software | SAM $100M — interactive poetry apps | SOM $8M — blockchain-driven dynamic poem tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MetaPoem" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create poems that evolve via blockchain triggers based on reader interactions or events. Discipline: Writing, Poetry & Narrative (dynamic poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts enable programmable, immutable poem state changes. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MetaPoem" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-authorshipchain-9 Title: AuthorshipChain Theme: Writing, Poetry & Narrative (writing) · writer identity Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Manage and verify author identities and pseudonyms securely through smart contract registries. Why Hedera: Onchain identity contracts prevent impersonation and ensure author authenticity. Market: TAM $1.5B — global writing tools | SAM $220M — author management platforms | SOM $16M — blockchain identity solutions for writers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "AuthorshipChain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and verify author identities and pseudonyms securely through smart contract registries. Discipline: Writing, Poetry & Narrative (writer identity). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain identity contracts prevent impersonation and ensure author authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "AuthorshipChain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plottoken-10 Title: PlotToken Theme: Writing, Poetry & Narrative (writing) · story licensing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Tokenize plot ideas for secure licensing and transfer to producers or collaborators. Why Hedera: Smart contracts guarantee ownership and limit unauthorized reuse through token control. Market: TAM $1.5B — writing and publishing markets | SAM $320M — story idea marketplaces | SOM $20M — blockchain tokenized plot licensing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotToken" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Tokenize plot ideas for secure licensing and transfer to producers or collaborators. Discipline: Writing, Poetry & Narrative (story licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts guarantee ownership and limit unauthorized reuse through token control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotToken" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptchainaudit-11 Title: ScriptChainAudit Theme: Writing, Poetry & Narrative (writing) · version tracking Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Track and audit screenplay versions with immutable blockchain timestamps for dispute resolution. Why Hedera: Hedera testnet contracts provide irrefutable, transparent version histories. Market: TAM $1.5B — screenwriting software | SAM $280M — script version control tools | SOM $22M — blockchain-based script audits ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptChainAudit" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Track and audit screenplay versions with immutable blockchain timestamps for dispute resolution. Discipline: Writing, Poetry & Narrative (version tracking). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts provide irrefutable, transparent version histories. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptChainAudit" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-freeversedao-12 Title: FreeVerseDAO Theme: Writing, Poetry & Narrative (writing) · poetry funding Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable decentralized funding and grant distribution for poets via DAO governance onchain. Why Hedera: Smart contracts transparently allocate funds based on community votes and milestones. Market: TAM $1.5B — writing grant platforms | SAM $140M — poetry crowdfunding | SOM $10M — blockchain poetry funding DAOs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FreeVerseDAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable decentralized funding and grant distribution for poets via DAO governance onchain. Discipline: Writing, Poetry & Narrative (poetry funding). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts transparently allocate funds based on community votes and milestones. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FreeVerseDAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrativebadge-13 Title: NarrativeBadge Theme: Writing, Poetry & Narrative (writing) · writer credentials Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Issue verifiable badges or certificates on Hedera testnet to recognize narrative design skills and achievements. Why Hedera: Smart contracts securely mint badges that cannot be falsified or revoked unfairly. Market: TAM $1.5B — education and writing tools | SAM $250M — digital credentialing | SOM $18M — blockchain verified writer credentials ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrativeBadge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Issue verifiable badges or certificates on Hedera testnet to recognize narrative design skills and achievements. Discipline: Writing, Poetry & Narrative (writer credentials). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts securely mint badges that cannot be falsified or revoked unfairly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrativeBadge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poemchainprint-14 Title: PoemChainPrint Theme: Writing, Poetry & Narrative (writing) · limited edition poetry Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create blockchain-verified limited edition poem prints with provable scarcity for collectors. Why Hedera: Immutable smart contracts verify scarcity and provenance of each print edition. Market: TAM $1.5B — writing and art markets | SAM $230M — limited edition poetry sales | SOM $15M — blockchain poetry collectibles ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoemChainPrint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create blockchain-verified limited edition poem prints with provable scarcity for collectors. Discipline: Writing, Poetry & Narrative (limited edition poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable smart contracts verify scarcity and provenance of each print edition. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoemChainPrint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrativestake-15 Title: NarrativeStake Theme: Writing, Poetry & Narrative (writing) · story contributor rewards Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Reward story contributors fairly by staking tokens linked to their contributions recorded onchain. Why Hedera: Hedera testnet contracts hold and distribute rewards based on transparent contribution metrics. Market: TAM $1.5B — collaborative writing software | SAM $190M — cooperative story apps | SOM $13M — blockchain contributor reward systems ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrativeStake" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Reward story contributors fairly by staking tokens linked to their contributions recorded onchain. Discipline: Writing, Poetry & Narrative (story contributor rewards). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts hold and distribute rewards based on transparent contribution metrics. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrativeStake" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptvote-16 Title: ScriptVote Theme: Writing, Poetry & Narrative (writing) · script feedback Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Collect and manage anonymous peer feedback on screenplays via secure onchain voting. Why Hedera: Smart contracts ensure transparent, tamper-proof voting results for constructive critique. Market: TAM $1.5B — screenwriting collaboration tools | SAM $210M — script review platforms | SOM $14M — blockchain feedback management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptVote" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collect and manage anonymous peer feedback on screenplays via secure onchain voting. Discipline: Writing, Poetry & Narrative (script feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts ensure transparent, tamper-proof voting results for constructive critique. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptVote" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetryescrow-17 Title: PoetryEscrow Theme: Writing, Poetry & Narrative (writing) · commissioned writing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Hold commissioned poem payments in escrow smart contracts until writer delivers and buyer approves. Why Hedera: Onchain escrow ensures trust and fairness in creative transactions. Market: TAM $1.5B — freelancing and writing tools | SAM $160M — poetry commission markets | SOM $11M — blockchain escrow for writers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoetryEscrow" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Hold commissioned poem payments in escrow smart contracts until writer delivers and buyer approves. Discipline: Writing, Poetry & Narrative (commissioned writing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Onchain escrow ensures trust and fairness in creative transactions. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoetryEscrow" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-characternft-18 Title: CharacterNFT Theme: Writing, Poetry & Narrative (writing) · narrative IP Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Create NFTs representing unique narrative characters to securely license and track derivative use. Why Hedera: Smart contracts validate exclusive character ownership and transfer rights. Market: TAM $1.5B — writing and IP markets | SAM $270M — character licensing platforms | SOM $20M — blockchain character IP management ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "CharacterNFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create NFTs representing unique narrative characters to securely license and track derivative use. Discipline: Writing, Poetry & Narrative (narrative IP). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts validate exclusive character ownership and transfer rights. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "CharacterNFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-dialoguedao-19 Title: DialogueDAO Theme: Writing, Poetry & Narrative (writing) · script collaboration Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Govern and reward co-writers contributing dialogue lines via blockchain DAO mechanisms. Why Hedera: Hedera testnet contracts automatically record and share governance and revenue fairly among writers. Market: TAM $1.5B — collaborative screenwriting tools | SAM $230M — writer collaboration platforms | SOM $16M — blockchain DAO for script writing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "DialogueDAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Govern and reward co-writers contributing dialogue lines via blockchain DAO mechanisms. Discipline: Writing, Poetry & Narrative (script collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts automatically record and share governance and revenue fairly among writers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "DialogueDAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetproofs-20 Title: PoetProofs Theme: Writing, Poetry & Narrative (writing) · draft notarization Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Notarize poem drafts with blockchain timestamps to establish authorship and protect IP rights. Why Hedera: Immutable onchain records provide trusted proof of creation time and authenticity. Market: TAM $1.5B — global writing tools | SAM $280M — IP protection services | SOM $18M — blockchain-based literary notarization ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoetProofs" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Notarize poem drafts with blockchain timestamps to establish authorship and protect IP rights. Discipline: Writing, Poetry & Narrative (draft notarization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable onchain records provide trusted proof of creation time and authenticity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoetProofs" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plotchainswap-21 Title: PlotChainSwap Theme: Writing, Poetry & Narrative (writing) · story idea exchange Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Enable decentralized swapping and barter of plot ideas secured and logged on blockchain. Why Hedera: Smart contracts facilitate trustless, transparent exchanges without intermediaries. Market: TAM $1.5B — writing marketplaces | SAM $120M — story idea platforms | SOM $9M — blockchain story swapping ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotChainSwap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable decentralized swapping and barter of plot ideas secured and logged on blockchain. Discipline: Writing, Poetry & Narrative (story idea exchange). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts facilitate trustless, transparent exchanges without intermediaries. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotChainSwap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrativemint-22 Title: NarrativeMint Theme: Writing, Poetry & Narrative (writing) · story tokenization Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Mint entire stories as tokens allowing ownership proof and fractional sales. Why Hedera: Hedera testnet smart contracts enable immutable story token minting and ownership tracking. Market: TAM $1.5B — digital publishing market | SAM $350M — eBook sales platforms | SOM $25M — blockchain story token marketplaces ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrativeMint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint entire stories as tokens allowing ownership proof and fractional sales. Discipline: Writing, Poetry & Narrative (story tokenization). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet smart contracts enable immutable story token minting and ownership tracking. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrativeMint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-screenplaystake-23 Title: ScreenplayStake Theme: Writing, Poetry & Narrative (writing) · crowd script reviewing Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Community members stake tokens to review scripts securely, incentivizing quality feedback. Why Hedera: Smart contracts manage stakes, rewards, and dispute resolution transparently. Market: TAM $1.5B — screenwriting communities | SAM $200M — script feedback networks | SOM $15M — blockchain incentivized reviewing ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScreenplayStake" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Community members stake tokens to review scripts securely, incentivizing quality feedback. Discipline: Writing, Poetry & Narrative (crowd script reviewing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Smart contracts manage stakes, rewards, and dispute resolution transparently. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScreenplayStake" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetrychaingift-24 Title: PoetryChainGift Theme: Writing, Poetry & Narrative (writing) · secure gifting Hedera hook: Hedera smart contract (HSCS) [onchain logic] Pitch: Gift poems securely onchain with immutable ownership and personalized provenance records. Why Hedera: Hedera testnet contracts guarantee provenance and transfer history in gifting scenarios. Market: TAM $1.5B — digital writing tools | SAM $180M — poetry gifting platforms | SOM $13M — blockchain poetry gifting solutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoetryChainGift" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Gift poems securely onchain with immutable ownership and personalized provenance records. Discipline: Writing, Poetry & Narrative (secure gifting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera testnet contracts guarantee provenance and transfer history in gifting scenarios. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoetryChainGift" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetic-memory-vault-0 Title: Poetic Memory Vault Theme: Writing, Poetry & Narrative (writing) · poetry archiving Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Permanently preserve poets' drafts and revisions for authentic literary heritage. Why Hedera: IPFS ensures immutable, decentralized storage of evolving poetic content via Pinata. Market: TAM $250M — global poetry market and archival services | SAM $60M — digital poetry preservation tools | SOM $10M — early adopters among professional poets and literary institutions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Poetic Memory Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Permanently preserve poets' drafts and revisions for authentic literary heritage. Discipline: Writing, Poetry & Narrative (poetry archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures immutable, decentralized storage of evolving poetic content via Pinata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Poetic Memory Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-flow-sync-1 Title: Narrative Flow Sync Theme: Writing, Poetry & Narrative (writing) · interactive storytelling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Save and share branching story manifests guaranteeing permanent access and collaboration. Why Hedera: IPFS via Pinata securely pins dynamic narrative manifests ensuring consistent version control. Market: TAM $500M — interactive storytelling software market | SAM $120M — narrative design tools for writers and developers | SOM $20M — indie interactive writers and small studios ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Flow Sync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Save and share branching story manifests guaranteeing permanent access and collaboration. Discipline: Writing, Poetry & Narrative (interactive storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata securely pins dynamic narrative manifests ensuring consistent version control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Flow Sync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-character-bios-hub-2 Title: Character Bios Hub Theme: Writing, Poetry & Narrative (writing) · character development Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store and distribute rich, immutable character profiles with multimedia attachments. Why Hedera: Pinata JWT upload to IPFS supports multi-format and permanent character asset pinning. Market: TAM $300M — character design and writing aids market | SAM $80M — digital character workbook apps | SOM $15M — professional authors and scriptwriters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Character Bios Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store and distribute rich, immutable character profiles with multimedia attachments. Discipline: Writing, Poetry & Narrative (character development). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT upload to IPFS supports multi-format and permanent character asset pinning. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Character Bios Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-verse-visualizer-3 Title: Verse Visualizer Theme: Writing, Poetry & Narrative (writing) · poetical imagery Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Upload and preserve poetic image metaphors linked to verse for lasting inspiration. Why Hedera: Pinata enables reliable pinning of paired image and JSON metadata representing poems. Market: TAM $200M — poetry and visual art crossover products | SAM $50M — multimedia poetry creation tools | SOM $8M — poet communities using digital illustration tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Verse Visualizer" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Upload and preserve poetic image metaphors linked to verse for lasting inspiration. Discipline: Writing, Poetry & Narrative (poetical imagery). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata enables reliable pinning of paired image and JSON metadata representing poems. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Verse Visualizer" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-screenplay-snapshot-4 Title: Screenplay Snapshot Theme: Writing, Poetry & Narrative (writing) · screenwriting versioning Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin your screenplay drafts with visual and metadata snapshots for secure historic reference. Why Hedera: IPFS via Pinata ensures permanent and tamper-proof screenplay version pinning. Market: TAM $400M — screenplay software market | SAM $100M — versioning and collaboration tools for screenwriters | SOM $18M — professional screenwriters and indie filmmakers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Screenplay Snapshot" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin your screenplay drafts with visual and metadata snapshots for secure historic reference. Discipline: Writing, Poetry & Narrative (screenwriting versioning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata ensures permanent and tamper-proof screenplay version pinning. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Screenplay Snapshot" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-map-archive-5 Title: Narrative Map Archive Theme: Writing, Poetry & Narrative (writing) · story mapping Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Securely store interactive narrative maps and their changes for narrative design teams. Why Hedera: Pinning JSON manifests on IPFS provides decentralization and permanence for story maps. Market: TAM $350M — narrative design and mapping software | SAM $90M — interactive story mapping platforms | SOM $12M — narrative designers in gaming and literature ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Map Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely store interactive narrative maps and their changes for narrative design teams. Discipline: Writing, Poetry & Narrative (story mapping). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinning JSON manifests on IPFS provides decentralization and permanence for story maps. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Map Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-writer-s-prompt-vault-6 Title: Writer’s Prompt Vault Theme: Writing, Poetry & Narrative (writing) · creative prompts Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Curate and preserve evolving writing prompts and challenges with immutable timestamps. Why Hedera: Pinata’s IPFS pinning creates permanent prompt archives that can’t be altered or lost. Market: TAM $150M — creative writing and education markets | SAM $40M — digital writing prompt apps | SOM $7M — writing coaches and writers' groups ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Writer’s Prompt Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Curate and preserve evolving writing prompts and challenges with immutable timestamps. Discipline: Writing, Poetry & Narrative (creative prompts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning creates permanent prompt archives that can’t be altered or lost. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Writer’s Prompt Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-token-ledger-7 Title: Narrative Token Ledger Theme: Writing, Poetry & Narrative (writing) · story ownership Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Register and prove narrative intellectual property rights with permanent onchain manifests. Why Hedera: Immutable IPFS pins via Pinata provide verifiable proof of story originality and timestamps. Market: TAM $1.5B — global writing tools and IP management | SAM $350M — narrative IP protection services | SOM $50M — professional authors and publishers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Token Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Register and prove narrative intellectual property rights with permanent onchain manifests. Discipline: Writing, Poetry & Narrative (story ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Immutable IPFS pins via Pinata provide verifiable proof of story originality and timestamps. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Token Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poem-remix-repository-8 Title: Poem Remix Repository Theme: Writing, Poetry & Narrative (writing) · poetry collaboration Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Host and pin collaborative poem remixes with full version history on decentralized storage. Why Hedera: Pinata guarantees all remixed poem versions remain permanently accessible on IPFS. Market: TAM $180M — collaborative creative software | SAM $45M — poetry collaboration platforms | SOM $6M — digital poet collectives and educators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Poem Remix Repository" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host and pin collaborative poem remixes with full version history on decentralized storage. Discipline: Writing, Poetry & Narrative (poetry collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata guarantees all remixed poem versions remain permanently accessible on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Poem Remix Repository" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-screenplay-beatboard-9 Title: Screenplay Beatboard Theme: Writing, Poetry & Narrative (writing) · plot structuring Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin structured screenplay beatboards and revisions immutably to maintain story consistency. Why Hedera: IPFS via Pinata securely stores serialized plot breakdowns with version control. Market: TAM $380M — script development and writing aids | SAM $95M — screenplay plotting apps | SOM $15M — professional screenwriters and script coordinators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Screenplay Beatboard" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin structured screenplay beatboards and revisions immutably to maintain story consistency. Discipline: Writing, Poetry & Narrative (plot structuring). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata securely stores serialized plot breakdowns with version control. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Screenplay Beatboard" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-soundtrack-archive-10 Title: Narrative Soundtrack Archive Theme: Writing, Poetry & Narrative (writing) · audio storytelling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Store narrative audio clips and story-related soundtracks permanently linked to story manifests. Why Hedera: Pinata supports pinning audio files with metadata immutably on IPFS for storytelling. Market: TAM $320M — audio storytelling and podcasting tools | SAM $85M — narrative audio creation platforms | SOM $14M — audio writers and narrative podcasters ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Soundtrack Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Store narrative audio clips and story-related soundtracks permanently linked to story manifests. Discipline: Writing, Poetry & Narrative (audio storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata supports pinning audio files with metadata immutably on IPFS for storytelling. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Soundtrack Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-metadata-story-weave-11 Title: Metadata Story Weave Theme: Writing, Poetry & Narrative (writing) · story metadata management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Attach and pin rich metadata sets to stories ensuring permanent discoverability and context. Why Hedera: Pinata JWT upload pins complex JSON metadata to IPFS for immutable story context. Market: TAM $450M — digital publishing metadata market | SAM $110M — metadata tools for writers and publishers | SOM $20M — narrative metadata specialists and authors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Metadata Story Weave" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Attach and pin rich metadata sets to stories ensuring permanent discoverability and context. Discipline: Writing, Poetry & Narrative (story metadata management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT upload pins complex JSON metadata to IPFS for immutable story context. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Metadata Story Weave" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-epic-poem-archive-12 Title: Epic Poem Archive Theme: Writing, Poetry & Narrative (writing) · long-form poetry Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Host epic poems and iterations permanently ensuring literary longevity and accessibility. Why Hedera: IPFS via Pinata provides decentralized, censorship-resistant storage for large poetic works. Market: TAM $270M — poetry book and digital archive market | SAM $65M — epic poetry digital platforms | SOM $9M — epic poets and archivists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Epic Poem Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host epic poems and iterations permanently ensuring literary longevity and accessibility. Discipline: Writing, Poetry & Narrative (long-form poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata provides decentralized, censorship-resistant storage for large poetic works. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Epic Poem Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-flash-fiction-cache-13 Title: Flash Fiction Cache Theme: Writing, Poetry & Narrative (writing) · short story publishing Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin flash fiction and micro-narratives ensuring permanent public access and author credit. Why Hedera: Pinata allows fast, immutable pinning of small story JSONs and cover art on IPFS. Market: TAM $220M — short story digital publishing market | SAM $55M — flash fiction platforms and apps | SOM $7M — flash fiction writers and publishers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Flash Fiction Cache" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin flash fiction and micro-narratives ensuring permanent public access and author credit. Discipline: Writing, Poetry & Narrative (short story publishing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata allows fast, immutable pinning of small story JSONs and cover art on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Flash Fiction Cache" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-collaborative-verse-chain-14 Title: Collaborative Verse Chain Theme: Writing, Poetry & Narrative (writing) · co-written poetry Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Immutable shared poetry projects pinned with full authorship and version control. Why Hedera: IPFS ensures no loss of collaborative poem versions via Pinata's reliable pinning service. Market: TAM $160M — collaborative writing software market | SAM $42M — multi-author poetry and text apps | SOM $5M — poet teams and workshops ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Collaborative Verse Chain" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Immutable shared poetry projects pinned with full authorship and version control. Discipline: Writing, Poetry & Narrative (co-written poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS ensures no loss of collaborative poem versions via Pinata's reliable pinning service. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Collaborative Verse Chain" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-artifact-ledger-15 Title: Narrative Artifact Ledger Theme: Writing, Poetry & Narrative (writing) · story props and lore Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Preserve story artifacts, lore entries and related media immutably linked to narratives. Why Hedera: Pinata uploads JSON/image assets on IPFS providing permanent lore provenance. Market: TAM $340M — transmedia storytelling support tools | SAM $75M — story world-building software | SOM $13M — narrative designers and transmedia authors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Artifact Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Preserve story artifacts, lore entries and related media immutably linked to narratives. Discipline: Writing, Poetry & Narrative (story props and lore). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata uploads JSON/image assets on IPFS providing permanent lore provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Artifact Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-writer-s-journal-vault-16 Title: Writer’s Journal Vault Theme: Writing, Poetry & Narrative (writing) · personal writing logs Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin personal writing journals and progress snapshots for secure, permanent archival. Why Hedera: Pinata ensures private journals are securely pinned on IPFS with privacy controls. Market: TAM $280M — digital journaling and writing apps market | SAM $70M — writer-specific journaling tools | SOM $10M — freelance authors and creative writers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Writer’s Journal Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin personal writing journals and progress snapshots for secure, permanent archival. Discipline: Writing, Poetry & Narrative (personal writing logs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata ensures private journals are securely pinned on IPFS with privacy controls. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Writer’s Journal Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-screenplay-casting-files-17 Title: Screenplay Casting Files Theme: Writing, Poetry & Narrative (writing) · casting and character assets Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin casting notes and character references permanently linked to screenplay projects. Why Hedera: Pinata JWT uploads preserve multi-format casting data immutably on IPFS. Market: TAM $370M — screenplay production support market | SAM $85M — casting and character management apps | SOM $14M — writers and casting directors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Screenplay Casting Files" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin casting notes and character references permanently linked to screenplay projects. Discipline: Writing, Poetry & Narrative (casting and character assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata JWT uploads preserve multi-format casting data immutably on IPFS. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Screenplay Casting Files" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-role-tracker-18 Title: Narrative Role Tracker Theme: Writing, Poetry & Narrative (writing) · role-based story design Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Immutable tracking of story roles and character arcs pinned for collaborative clarity. Why Hedera: IPFS via Pinata pins JSON role manifests securely ensuring consistent team access. Market: TAM $300M — collaborative story tools market | SAM $75M — role and arc mapping apps | SOM $11M — narrative teams and writers’ rooms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Role Tracker" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Immutable tracking of story roles and character arcs pinned for collaborative clarity. Discipline: Writing, Poetry & Narrative (role-based story design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS via Pinata pins JSON role manifests securely ensuring consistent team access. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Role Tracker" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetry-event-ledger-19 Title: Poetry Event Ledger Theme: Writing, Poetry & Narrative (writing) · live poetry events Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin event schedules, recordings and poems from live readings for lasting access. Why Hedera: Pinata pins multimedia event data on IPFS to preserve cultural moments immutably. Market: TAM $130M — poetry event and digital archiving market | SAM $35M — live reading streaming and archive platforms | SOM $6M — poetry event organizers and audiences ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Poetry Event Ledger" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin event schedules, recordings and poems from live readings for lasting access. Discipline: Writing, Poetry & Narrative (live poetry events). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata pins multimedia event data on IPFS to preserve cultural moments immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Poetry Event Ledger" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-device-index-20 Title: Narrative Device Index Theme: Writing, Poetry & Narrative (writing) · literary device cataloging Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Create permanent indexes of literary devices used in stories with examples and annotations. Why Hedera: Pinata’s IPFS pinning preserves searchable JSON device catalogs and linked media. Market: TAM $210M — literary analysis and education software | SAM $50M — annotation and cataloging apps for writers | SOM $8M — educators and authors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Device Index" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create permanent indexes of literary devices used in stories with examples and annotations. Discipline: Writing, Poetry & Narrative (literary device cataloging). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata’s IPFS pinning preserves searchable JSON device catalogs and linked media. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Device Index" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-crowdwritten-saga-21 Title: Crowdwritten Saga Theme: Writing, Poetry & Narrative (writing) · crowdsourced storytelling Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin evolving crowd-sourced story manifests for shared narrative ownership and history. Why Hedera: IPFS with Pinata ensures every crowd addition is permanently stored with provenance. Market: TAM $400M — crowdsourced content creation market | SAM $100M — crowdwriting platforms | SOM $15M — participatory writing communities ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Crowdwritten Saga" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin evolving crowd-sourced story manifests for shared narrative ownership and history. Discipline: Writing, Poetry & Narrative (crowdsourced storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS with Pinata ensures every crowd addition is permanently stored with provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Crowdwritten Saga" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-script-location-scouting-22 Title: Script Location Scouting Theme: Writing, Poetry & Narrative (writing) · visual storytelling assets Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin images and notes of real locations linked to screenplay scenes immutably. Why Hedera: Pinata uploads combine images and JSON on IPFS securing location scouting data. Market: TAM $350M — film production planning software | SAM $85M — location management tools for writers | SOM $14M — screenwriters and production teams ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Script Location Scouting" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin images and notes of real locations linked to screenplay scenes immutably. Discipline: Writing, Poetry & Narrative (visual storytelling assets). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata uploads combine images and JSON on IPFS securing location scouting data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Script Location Scouting" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-story-genre-taxonomy-23 Title: Story Genre Taxonomy Theme: Writing, Poetry & Narrative (writing) · genre classification Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin permanent genre taxonomies and story type metadata for consistent classification. Why Hedera: IPFS supports immutable taxonomies with Pinata ensuring provenance of genre data. Market: TAM $180M — publishing classification and metadata market | SAM $45M — genre tagging software for authors | SOM $7M — publishers and narrative platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Story Genre Taxonomy" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin permanent genre taxonomies and story type metadata for consistent classification. Discipline: Writing, Poetry & Narrative (genre classification). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: IPFS supports immutable taxonomies with Pinata ensuring provenance of genre data. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Story Genre Taxonomy" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-timeline-archive-24 Title: Narrative Timeline Archive Theme: Writing, Poetry & Narrative (writing) · chronology management Hedera hook: IPFS via Pinata [decentralized storage] Pitch: Pin chronological story timelines with events and character arcs permanently stored. Why Hedera: Pinata securely pins timeline JSONs to IPFS preserving story chronology over time. Market: TAM $320M — story planning and timeline tools | SAM $80M — timeline management apps for writers | SOM $12M — novelists, scriptwriters, and gamers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Timeline Archive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pin chronological story timelines with events and character arcs permanently stored. Discipline: Writing, Poetry & Narrative (chronology management). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Pinata securely pins timeline JSONs to IPFS preserving story chronology over time. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Timeline Archive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-versevault-0 Title: VerseVault Theme: Writing, Poetry & Narrative (writing) · poetry drafting Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely draft and store poems with seamless gasless transaction experience. Why Hedera: Hedera's fixed sub-cent fees eliminate gas costs, ensuring effortless poem versioning and edits. Market: TAM $500M — global poetry tool market | SAM $120M — digital poetry drafting tools | SOM $15M — active users of gasless drafting apps ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseVault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely draft and store poems with seamless gasless transaction experience. Discipline: Writing, Poetry & Narrative (poetry drafting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees eliminate gas costs, ensuring effortless poem versioning and edits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseVault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-storystake-1 Title: StoryStake Theme: Writing, Poetry & Narrative (writing) · interactive narrative Hedera hook: Magic Link email wallet [wallet UX] Pitch: Engage writers in creating branching stories with frictionless onchain ownership of choices. Why Hedera: Magic Link email sign-in plus gasless transactions enables real-time user participation without blockchain friction. Market: TAM $1.2B — interactive storytelling software | SAM $300M — branching narrative design tools | SOM $40M — writers adopting decentralized story platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StoryStake" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Engage writers in creating branching stories with frictionless onchain ownership of choices. Discipline: Writing, Poetry & Narrative (interactive narrative). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus gasless transactions enables real-time user participation without blockchain friction. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StoryStake" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptsponsor-2 Title: ScriptSponsor Theme: Writing, Poetry & Narrative (writing) · screenwriting collaboration Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collaborate on scripts with transparent, gasless edits tracked via embedded wallets. Why Hedera: Hedera's fixed sub-cent fees ensure no gas fees interrupt smooth multi-author script edits. Market: TAM $700M — screenwriting software market | SAM $180M — collaborative script tools | SOM $25M — screenwriters using integrated onchain edits ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptSponsor" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaborate on scripts with transparent, gasless edits tracked via embedded wallets. Discipline: Writing, Poetry & Narrative (screenwriting collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees ensure no gas fees interrupt smooth multi-author script edits. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptSponsor" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narratenexus-3 Title: NarrateNexus Theme: Writing, Poetry & Narrative (writing) · narrative design Hedera hook: Magic Link email wallet [wallet UX] Pitch: Create and monetize narrative assets securely with seamless wallet integration and no fees. Why Hedera: Magic Link email sign-in enables easy payments and asset transfers without user gas burden. Market: TAM $900M — narrative design software | SAM $230M — digital narrative asset market | SOM $30M — narrative designers using blockchain tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrateNexus" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create and monetize narrative assets securely with seamless wallet integration and no fees. Discipline: Writing, Poetry & Narrative (narrative design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in enables easy payments and asset transfers without user gas burden. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrateNexus" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poemproof-4 Title: PoemProof Theme: Writing, Poetry & Narrative (writing) · poetry copyright Hedera hook: Magic Link email wallet [wallet UX] Pitch: Instantly timestamp poems with zero gas to prove authorship and originality. Why Hedera: Hedera's fixed sub-cent fees allow effortless onchain proof of poem creation without cost barriers. Market: TAM $400M — copyright tools for writers | SAM $100M — digital rights management | SOM $12M — poets adopting onchain copyright ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoemProof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Instantly timestamp poems with zero gas to prove authorship and originality. Discipline: Writing, Poetry & Narrative (poetry copyright). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees allow effortless onchain proof of poem creation without cost barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoemProof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-versevoyage-5 Title: VerseVoyage Theme: Writing, Poetry & Narrative (writing) · poetry contests Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host and join gasless onchain poetry contests with social verification and prizes. Why Hedera: Gasless Hedera's fixed sub-cent feess enable fair competition entry and reward distribution without cost. Market: TAM $600M — poetry contest platforms | SAM $150M — digital contest management | SOM $20M — active contest participants onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseVoyage" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host and join gasless onchain poetry contests with social verification and prizes. Discipline: Writing, Poetry & Narrative (poetry contests). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Gasless Hedera's fixed sub-cent feess enable fair competition entry and reward distribution without cost. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseVoyage" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-fictionfuel-6 Title: FictionFuel Theme: Writing, Poetry & Narrative (writing) · story ideation Hedera hook: Magic Link email wallet [wallet UX] Pitch: Collaborate on story ideas privately with gasless transactions to ensure smooth feedback flow. Why Hedera: Magic Link email sign-in enables private idea exchange without hassle of paying gas fees. Market: TAM $1B — global writing ideation tools | SAM $275M — collaborative story development | SOM $35M — writers using private ideation wallets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "FictionFuel" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Collaborate on story ideas privately with gasless transactions to ensure smooth feedback flow. Discipline: Writing, Poetry & Narrative (story ideation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in enables private idea exchange without hassle of paying gas fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "FictionFuel" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptsync-7 Title: ScriptSync Theme: Writing, Poetry & Narrative (writing) · screenplay versioning Hedera hook: Magic Link email wallet [wallet UX] Pitch: Version screenplays with transparent, sponsored blockchain transactions for seamless updates. Why Hedera: Hedera's fixed sub-cent fees remove friction in multi-version screenplay updates onchain. Market: TAM $650M — screenplay management tools | SAM $160M — version control software | SOM $22M — screenwriters using blockchain versioning ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptSync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Version screenplays with transparent, sponsored blockchain transactions for seamless updates. Discipline: Writing, Poetry & Narrative (screenplay versioning). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees remove friction in multi-version screenplay updates onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptSync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrativenest-8 Title: NarrativeNest Theme: Writing, Poetry & Narrative (writing) · world-building Hedera hook: Magic Link email wallet [wallet UX] Pitch: Build and own expansive world lore collaboratively with gasless onchain edits and storage. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows collaborative lore creation without blockchain fees. Market: TAM $800M — world-building tools market | SAM $200M — narrative collaboration platforms | SOM $28M — world-builders leveraging onchain tech ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrativeNest" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Build and own expansive world lore collaboratively with gasless onchain edits and storage. Discipline: Writing, Poetry & Narrative (world-building). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees allows collaborative lore creation without blockchain fees. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrativeNest" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-versevouch-9 Title: VerseVouch Theme: Writing, Poetry & Narrative (writing) · poetry endorsement Hedera hook: Magic Link email wallet [wallet UX] Pitch: Endorse favorite poems effortlessly with gasless transactions linked to your social identity. Why Hedera: Hedera's fixed sub-cent feess tied to Magic Link email sign-in enable smooth, cost-free poem endorsements. Market: TAM $450M — poetry discovery platforms | SAM $110M — digital endorsement tools | SOM $14M — poets receiving onchain vouches ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseVouch" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Endorse favorite poems effortlessly with gasless transactions linked to your social identity. Discipline: Writing, Poetry & Narrative (poetry endorsement). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent feess tied to Magic Link email sign-in enable smooth, cost-free poem endorsements. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseVouch" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plotpledge-10 Title: PlotPledge Theme: Writing, Poetry & Narrative (writing) · crowdfund writing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Pledge funds to promising stories with seamless, gasless blockchain sponsorship wallets. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees streamlines writer crowdfunding without gas hurdles. Market: TAM $1B — writer crowdfunding markets | SAM $270M — digital patronage tools | SOM $33M — writers funded on gasless platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotPledge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Pledge funds to promising stories with seamless, gasless blockchain sponsorship wallets. Discipline: Writing, Poetry & Narrative (crowdfund writing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees streamlines writer crowdfunding without gas hurdles. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotPledge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narratenft-11 Title: NarrateNFT Theme: Writing, Poetry & Narrative (writing) · narrative NFTs Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint story elements as NFTs with embedded, gasless wallet integration for easy ownership. Why Hedera: Hedera's fixed sub-cent fees minimize cost barriers in minting and transferring narrative NFTs. Market: TAM $900M — NFT writing markets | SAM $220M — literary NFT sales | SOM $27M — writers minting gasless NFTs ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrateNFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint story elements as NFTs with embedded, gasless wallet integration for easy ownership. Discipline: Writing, Poetry & Narrative (narrative NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees minimize cost barriers in minting and transferring narrative NFTs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrateNFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptsponsorswap-12 Title: ScriptSponsorSwap Theme: Writing, Poetry & Narrative (writing) · screenwriter patronage Hedera hook: Magic Link email wallet [wallet UX] Pitch: Enable patrons to sponsor screenwriters with gasless, secure onchain wallet transactions. Why Hedera: the embedded wallet powered wallet with Hedera's fixed sub-cent fees ensures seamless, no-cost patronage flows. Market: TAM $750M — screenwriter support tools | SAM $180M — creator sponsorship platforms | SOM $24M — screenwriters receiving onchain support ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptSponsorSwap" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Enable patrons to sponsor screenwriters with gasless, secure onchain wallet transactions. Discipline: Writing, Poetry & Narrative (screenwriter patronage). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet powered wallet with Hedera's fixed sub-cent fees ensures seamless, no-cost patronage flows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptSponsorSwap" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-versevaultlive-13 Title: VerseVaultLive Theme: Writing, Poetry & Narrative (writing) · live poetry Hedera hook: Magic Link email wallet [wallet UX] Pitch: Host live poetry writing sessions with instant, gasless collaborative edits and social interaction. Why Hedera: Gasless Hedera's fixed sub-cent fees allow uninterrupted, real-time collaborative poetry creation. Market: TAM $550M — live writing platforms | SAM $130M — live poetry collaboration | SOM $18M — poets engaging in live gasless sessions ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseVaultLive" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Host live poetry writing sessions with instant, gasless collaborative edits and social interaction. Discipline: Writing, Poetry & Narrative (live poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Gasless Hedera's fixed sub-cent fees allow uninterrupted, real-time collaborative poetry creation. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseVaultLive" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrativenodes-14 Title: NarrativeNodes Theme: Writing, Poetry & Narrative (writing) · story graph design Hedera hook: Magic Link email wallet [wallet UX] Pitch: Design interconnected story graphs collaboratively with frictionless onchain edits and ownership. Why Hedera: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless multi-user graph changes without gas. Market: TAM $850M — story design software | SAM $210M — collaborative graph tools | SOM $26M — narrative designers on blockchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrativeNodes" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Design interconnected story graphs collaboratively with frictionless onchain edits and ownership. Discipline: Writing, Poetry & Narrative (story graph design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-ins plus Hedera's fixed sub-cent fees enable seamless multi-user graph changes without gas. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrativeNodes" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-echoedit-15 Title: EchoEdit Theme: Writing, Poetry & Narrative (writing) · peer feedback Hedera hook: Magic Link email wallet [wallet UX] Pitch: Exchange onchain peer critiques with gasless transactions tied to verified social identities. Why Hedera: Hedera's fixed sub-cent fees plus Magic Link email sign-in enables cost-free, traceable feedback exchanges. Market: TAM $700M — writer feedback platforms | SAM $160M — peer critique ecosystems | SOM $20M — writers using onchain feedback ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EchoEdit" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Exchange onchain peer critiques with gasless transactions tied to verified social identities. Discipline: Writing, Poetry & Narrative (peer feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees plus Magic Link email sign-in enables cost-free, traceable feedback exchanges. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EchoEdit" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plotpassport-16 Title: PlotPassport Theme: Writing, Poetry & Narrative (writing) · story ownership Hedera hook: Magic Link email wallet [wallet UX] Pitch: Securely register story beginnings onchain with gasless wallet transactions for easy proof. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees allow authors to claim story rights cost-free and instantly. Market: TAM $600M — story IP registration | SAM $140M — digital story proofing | SOM $17M — writers registering plots onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotPassport" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely register story beginnings onchain with gasless wallet transactions for easy proof. Discipline: Writing, Poetry & Narrative (story ownership). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees allow authors to claim story rights cost-free and instantly. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotPassport" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-versevibe-17 Title: VerseVibe Theme: Writing, Poetry & Narrative (writing) · poetry social feed Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share and tip poems in a gasless, social wallet environment to build fan engagement. Why Hedera: Hedera's fixed sub-cent feess with Magic Link email sign-in facilitate seamless social interactions and tipping without gas. Market: TAM $650M — social poetry platforms | SAM $155M — tipping and sharing tools | SOM $19M — poets monetizing gaslessly ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseVibe" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share and tip poems in a gasless, social wallet environment to build fan engagement. Discipline: Writing, Poetry & Narrative (poetry social feed). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent feess with Magic Link email sign-in facilitate seamless social interactions and tipping without gas. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseVibe" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptstand-18 Title: ScriptStand Theme: Writing, Poetry & Narrative (writing) · script marketplace Hedera hook: Magic Link email wallet [wallet UX] Pitch: Buy and sell scripts with frictionless, gasless onchain payments linked to social wallets. Why Hedera: Magic Link email sign-in with Hedera's fixed sub-cent fees simplifies script transactions without cost barriers. Market: TAM $800M — screenplay marketplaces | SAM $200M — digital script sales | SOM $25M — screenwriters transacting onchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptStand" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Buy and sell scripts with frictionless, gasless onchain payments linked to social wallets. Discipline: Writing, Poetry & Narrative (script marketplace). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in with Hedera's fixed sub-cent fees simplifies script transactions without cost barriers. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptStand" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-storysprint-19 Title: StorySprint Theme: Writing, Poetry & Narrative (writing) · timed writing Hedera hook: Magic Link email wallet [wallet UX] Pitch: Participate in timed story challenges with instant, gasless onchain submissions and social proof. Why Hedera: Hedera's fixed sub-cent fees remove entry friction for writers in blockchain-backed contests. Market: TAM $550M — timed writing platforms | SAM $130M — writing challenge ecosystems | SOM $16M — active participants in gasless contests ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "StorySprint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Participate in timed story challenges with instant, gasless onchain submissions and social proof. Discipline: Writing, Poetry & Narrative (timed writing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees remove entry friction for writers in blockchain-backed contests. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "StorySprint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-echoessays-20 Title: EchoEssays Theme: Writing, Poetry & Narrative (writing) · narrative essays Hedera hook: Magic Link email wallet [wallet UX] Pitch: Publish and securely timestamp essays with gasless wallet transactions for provenance. Why Hedera: the embedded wallet and Hedera's fixed sub-cent fees ensure effortless essay proofing onchain without gas costs. Market: TAM $500M — essay publishing tools | SAM $120M — digital provenance software | SOM $14M — essayists using blockchain ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "EchoEssays" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Publish and securely timestamp essays with gasless wallet transactions for provenance. Discipline: Writing, Poetry & Narrative (narrative essays). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: the embedded wallet and Hedera's fixed sub-cent fees ensure effortless essay proofing onchain without gas costs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "EchoEssays" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-versevest-21 Title: VerseVest Theme: Writing, Poetry & Narrative (writing) · poetry royalties Hedera hook: Magic Link email wallet [wallet UX] Pitch: Manage and distribute poetry royalties transparently via gasless blockchain transactions. Why Hedera: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplifies fair royalty splits without gas burden. Market: TAM $700M — royalty management tools | SAM $180M — poetry royalty systems | SOM $22M — poets on gasless royalty platforms ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "VerseVest" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Manage and distribute poetry royalties transparently via gasless blockchain transactions. Discipline: Writing, Poetry & Narrative (poetry royalties). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in plus Hedera's fixed sub-cent fees simplifies fair royalty splits without gas burden. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "VerseVest" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narratenestnft-22 Title: NarrateNestNFT Theme: Writing, Poetry & Narrative (writing) · world-building NFTs Hedera hook: Magic Link email wallet [wallet UX] Pitch: Mint and trade world-building NFTs with seamless, gasless wallet integration. Why Hedera: Hedera's fixed sub-cent fees minimize minting and transfer costs for expansive narrative NFTs. Market: TAM $900M — NFT world-building market | SAM $230M — digital lore NFTs | SOM $28M — narrative communities using NFT tools ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "NarrateNestNFT" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint and trade world-building NFTs with seamless, gasless wallet integration. Discipline: Writing, Poetry & Narrative (world-building NFTs). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees minimize minting and transfer costs for expansive narrative NFTs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "NarrateNestNFT" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plotpulse-23 Title: PlotPulse Theme: Writing, Poetry & Narrative (writing) · story analytics Hedera hook: Magic Link email wallet [wallet UX] Pitch: Analyze reader engagement on stories with gasless onchain interaction tracking. Why Hedera: Magic Link email sign-in and Hedera's fixed sub-cent fees enable cost-free interaction analytics onchain. Market: TAM $750M — story analytics tools | SAM $190M — reader engagement software | SOM $23M — narrative analytics users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotPulse" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Analyze reader engagement on stories with gasless onchain interaction tracking. Discipline: Writing, Poetry & Narrative (story analytics). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Magic Link email sign-in and Hedera's fixed sub-cent fees enable cost-free interaction analytics onchain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotPulse" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptsphere-24 Title: ScriptSphere Theme: Writing, Poetry & Narrative (writing) · script feedback Hedera hook: Magic Link email wallet [wallet UX] Pitch: Share scripts for peer review with gasless, onchain social wallet transactions. Why Hedera: Hedera's fixed sub-cent fees with Magic Link email sign-in ensures seamless, costless script feedback exchange. Market: TAM $700M — script review platforms | SAM $170M — collaborative feedback tools | SOM $21M — screenwriters getting onchain critiques ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptSphere" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Share scripts for peer review with gasless, onchain social wallet transactions. Discipline: Writing, Poetry & Narrative (script feedback). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: Hedera's fixed sub-cent fees with Magic Link email sign-in ensures seamless, costless script feedback exchange. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptSphere" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-verse-vault-0 Title: Verse Vault Theme: Writing, Poetry & Narrative (writing) · poetry archiving Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Securely mint and prove ownership of original poetry on-chain for authentic literary legacy. Why Hedera: NFT provenance ensures unalterable proof of poem origin and creator identity. Market: TAM $1.5B — global writing tools market | SAM $150M — poetry-focused digital tools segment | SOM $15M — dedicated NFT poetry archiving users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Verse Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Securely mint and prove ownership of original poetry on-chain for authentic literary legacy. Discipline: Writing, Poetry & Narrative (poetry archiving). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures unalterable proof of poem origin and creator identity. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Verse Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-nexus-1 Title: Narrative Nexus Theme: Writing, Poetry & Narrative (writing) · interactive storytelling Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint branching story nodes as NFTs to ensure unique creator control and provenance. Why Hedera: HTS NFT tokens anchor each story branch immutably on-chain with IPFS content. Market: TAM $1.5B — writing tools market globally | SAM $200M — interactive narrative software users | SOM $25M — NFT-based story branching creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Nexus" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint branching story nodes as NFTs to ensure unique creator control and provenance. Discipline: Writing, Poetry & Narrative (interactive storytelling). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens anchor each story branch immutably on-chain with IPFS content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Nexus" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-script-stamp-2 Title: Script Stamp Theme: Writing, Poetry & Narrative (writing) · screenwriting rights Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint screenplay drafts as NFTs to certify original authorship and version history. Why Hedera: NFT minting records screenplay IPFS CIDs providing transparent ownership trails. Market: TAM $1.5B — writing tools market | SAM $300M — screenwriting software users | SOM $30M — NFT screenplay rights minting clients ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Script Stamp" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint screenplay drafts as NFTs to certify original authorship and version history. Discipline: Writing, Poetry & Narrative (screenwriting rights). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT minting records screenplay IPFS CIDs providing transparent ownership trails. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Script Stamp" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poet-s-provenance-3 Title: Poet’s Provenance Theme: Writing, Poetry & Narrative (writing) · lyric poetry Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Create unique NFT tokens for lyric poems, certifying creators and preserving provenance. Why Hedera: HTS NFT on Hedera testnet secures immutable poet ownership metadata. Market: TAM $1.5B — global writing tools market | SAM $120M — lyric poetry tools and communities | SOM $12M — NFT lyric poetry minting users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Poet’s Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Create unique NFT tokens for lyric poems, certifying creators and preserving provenance. Discipline: Writing, Poetry & Narrative (lyric poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT on Hedera testnet secures immutable poet ownership metadata. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Poet’s Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-fable-frame-4 Title: Fable Frame Theme: Writing, Poetry & Narrative (writing) · children’s narratives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint children’s story drafts as NFTs to track authorship and distribution rights. Why Hedera: NFT provenance provides transparent proof of original content and creator. Market: TAM $1.5B — writing tools market | SAM $80M — children’s book writing digital tools | SOM $8M — NFT children’s story ownership clients ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fable Frame" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint children’s story drafts as NFTs to track authorship and distribution rights. Discipline: Writing, Poetry & Narrative (children’s narratives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance provides transparent proof of original content and creator. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fable Frame" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-epic-editions-5 Title: Epic Editions Theme: Writing, Poetry & Narrative (writing) · long-form fiction Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint chapters of novels as NFTs to prove authorship and track unpublished drafts. Why Hedera: HTS NFT tokens anchor each chapter’s IPFS CID immutably on-chain. Market: TAM $1.5B — global writing tools market | SAM $250M — novel writing software users | SOM $25M — NFT chapter provenance creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Epic Editions" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint chapters of novels as NFTs to prove authorship and track unpublished drafts. Discipline: Writing, Poetry & Narrative (long-form fiction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens anchor each chapter’s IPFS CID immutably on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Epic Editions" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-freeverse-forge-6 Title: Freeverse Forge Theme: Writing, Poetry & Narrative (writing) · experimental poetry Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint free verse poems as creator-owned NFTs with guaranteed provenance and originality. Why Hedera: NFTs provide secure, decentralized proof of authorship for unique poetic works. Market: TAM $1.5B — writing tools market | SAM $100M — experimental poetry digital tools | SOM $10M — NFT minting by free verse poets ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Freeverse Forge" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint free verse poems as creator-owned NFTs with guaranteed provenance and originality. Discipline: Writing, Poetry & Narrative (experimental poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs provide secure, decentralized proof of authorship for unique poetic works. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Freeverse Forge" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-dialogue-drop-7 Title: Dialogue Drop Theme: Writing, Poetry & Narrative (writing) · scriptwriting dialogue Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint dialogues as NFTs to certify creation and facilitate transparent rights management. Why Hedera: HTS NFT provenance tokens link dialogues to creators immutably on-chain. Market: TAM $1.5B — writing tools market | SAM $220M — scriptwriting software segment | SOM $22M — NFT dialogue ownership users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dialogue Drop" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint dialogues as NFTs to certify creation and facilitate transparent rights management. Discipline: Writing, Poetry & Narrative (scriptwriting dialogue). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT provenance tokens link dialogues to creators immutably on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dialogue Drop" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-mythos-mint-8 Title: Mythos Mint Theme: Writing, Poetry & Narrative (writing) · world-building lore Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint lore pieces as NFTs to secure original world-building content and creator identity. Why Hedera: NFT provenance anchors creator-owned lore on-chain with IPFS verification. Market: TAM $1.5B — global writing tools market | SAM $90M — world-building and lore creation tools | SOM $9M — NFT minting for lore creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Mythos Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint lore pieces as NFTs to secure original world-building content and creator identity. Discipline: Writing, Poetry & Narrative (world-building lore). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance anchors creator-owned lore on-chain with IPFS verification. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Mythos Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-sonnet-seal-9 Title: Sonnet Seal Theme: Writing, Poetry & Narrative (writing) · classic poetry Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint sonnets as NFTs to certify original poetic works and preserve ownership rights. Why Hedera: HTS NFT tokens provide tamper-proof sonnet provenance on Hedera testnet. Market: TAM $1.5B — writing tools market | SAM $85M — classic poetry digital tools | SOM $8.5M — NFT sonnet minting creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Sonnet Seal" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint sonnets as NFTs to certify original poetic works and preserve ownership rights. Discipline: Writing, Poetry & Narrative (classic poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide tamper-proof sonnet provenance on Hedera testnet. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Sonnet Seal" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plotpoint-proof-10 Title: PlotPoint Proof Theme: Writing, Poetry & Narrative (writing) · story plotting Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint plot outlines as NFTs to prove original story structures and creator authorship. Why Hedera: NFT provenance ensures immutable proof of plot ownership and content. Market: TAM $1.5B — writing tools market | SAM $140M — story plotting software users | SOM $14M — NFT plot outline owners ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PlotPoint Proof" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint plot outlines as NFTs to prove original story structures and creator authorship. Discipline: Writing, Poetry & Narrative (story plotting). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures immutable proof of plot ownership and content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PlotPoint Proof" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-haiku-hub-11 Title: Haiku Hub Theme: Writing, Poetry & Narrative (writing) · micro-poetry Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint haiku poems as unique NFTs to secure authorship with decentralized verification. Why Hedera: HTS NFT tokens link haikus to IPFS content ensuring creator provenance. Market: TAM $1.5B — global writing tools market | SAM $60M — micro-poetry tools market | SOM $6M — NFT haiku minting users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Haiku Hub" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint haiku poems as unique NFTs to secure authorship with decentralized verification. Discipline: Writing, Poetry & Narrative (micro-poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens link haikus to IPFS content ensuring creator provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Haiku Hub" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-scriptsync-12 Title: ScriptSync Theme: Writing, Poetry & Narrative (writing) · collaborative scripts Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint collaborative screenplay segments as NFTs to prove multi-author ownership securely. Why Hedera: NFT provenance tracks multiple creators with immutable on-chain proofs. Market: TAM $1.5B — writing tools market | SAM $180M — collaborative scriptwriting software | SOM $18M — NFT collaborative ownership clients ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "ScriptSync" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint collaborative screenplay segments as NFTs to prove multi-author ownership securely. Discipline: Writing, Poetry & Narrative (collaborative scripts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance tracks multiple creators with immutable on-chain proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "ScriptSync" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-prose-provenance-13 Title: Prose Provenance Theme: Writing, Poetry & Narrative (writing) · short story writing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint short stories as NFTs to validate originality and protect author rights. Why Hedera: HTS NFT tokens provide unforgeable proof of story ownership and content. Market: TAM $1.5B — writing tools market | SAM $130M — short story digital tools users | SOM $13M — NFT short story minting authors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Prose Provenance" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint short stories as NFTs to validate originality and protect author rights. Discipline: Writing, Poetry & Narrative (short story writing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide unforgeable proof of story ownership and content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Prose Provenance" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-memoir-mint-14 Title: Memoir Mint Theme: Writing, Poetry & Narrative (writing) · personal narratives Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint memoir excerpts as NFTs to preserve authentic creator ownership and history. Why Hedera: NFT provenance on Hedera testnet guarantees immutable creator proof for personal tales. Market: TAM $1.5B — global writing tools market | SAM $70M — memoir writing digital tools | SOM $7M — NFT memoir minting users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Memoir Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint memoir excerpts as NFTs to preserve authentic creator ownership and history. Discipline: Writing, Poetry & Narrative (personal narratives). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance on Hedera testnet guarantees immutable creator proof for personal tales. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Memoir Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-lyric-link-15 Title: Lyric Link Theme: Writing, Poetry & Narrative (writing) · songwriting lyrics Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint song lyrics as NFTs to certify authorship and protect intellectual property. Why Hedera: HTS NFT tokens anchor lyrics’ IPFS CIDs ensuring creator provenance. Market: TAM $1.5B — writing tools market | SAM $110M — songwriting tools segment | SOM $11M — NFT lyric ownership creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Lyric Link" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint song lyrics as NFTs to certify authorship and protect intellectual property. Discipline: Writing, Poetry & Narrative (songwriting lyrics). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens anchor lyrics’ IPFS CIDs ensuring creator provenance. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Lyric Link" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrator-s-nod-16 Title: Narrator’s Nod Theme: Writing, Poetry & Narrative (writing) · audiobook scripts Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint audiobook scripts as NFTs to prove original authorship and distribution rights. Why Hedera: NFT provenance secures scripts immutably on-chain with IPFS storage. Market: TAM $1.5B — writing tools market | SAM $90M — audiobook scriptwriting tools | SOM $9M — NFT minting audiobook authors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrator’s Nod" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint audiobook scripts as NFTs to prove original authorship and distribution rights. Discipline: Writing, Poetry & Narrative (audiobook scripts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance secures scripts immutably on-chain with IPFS storage. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrator’s Nod" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-plot-mint-17 Title: Plot Mint Theme: Writing, Poetry & Narrative (writing) · story idea validation Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint story concepts as NFTs to validate originality and stake creative claims. Why Hedera: HTS NFT tokens provide early immutable proof of concept and ownership. Market: TAM $1.5B — global writing tools market | SAM $150M — story ideation tool users | SOM $15M — NFT story concept validators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Plot Mint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint story concepts as NFTs to validate originality and stake creative claims. Discipline: Writing, Poetry & Narrative (story idea validation). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide early immutable proof of concept and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Plot Mint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-epic-endorse-18 Title: Epic Endorse Theme: Writing, Poetry & Narrative (writing) · literary peer review Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint peer review notes as NFTs to transparently acknowledge contributions to works. Why Hedera: NFT provenance tracks reviewer identity and feedback immutably. Market: TAM $1.5B — writing tools market | SAM $50M — literary review software users | SOM $5M — NFT peer reviewers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Epic Endorse" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint peer review notes as NFTs to transparently acknowledge contributions to works. Discipline: Writing, Poetry & Narrative (literary peer review). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance tracks reviewer identity and feedback immutably. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Epic Endorse" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-narrative-nexus-19 Title: Narrative Nexus Theme: Writing, Poetry & Narrative (writing) · game narrative design Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint branching game story elements as NFTs to track creators and version history. Why Hedera: HTS NFT tokens anchor unique narrative nodes securely on-chain. Market: TAM $1.5B — writing tools market | SAM $170M — game writing software users | SOM $17M — NFT game narrative designers ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Narrative Nexus" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint branching game story elements as NFTs to track creators and version history. Discipline: Writing, Poetry & Narrative (game narrative design). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens anchor unique narrative nodes securely on-chain. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Narrative Nexus" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-poetprints-20 Title: PoetPrints Theme: Writing, Poetry & Narrative (writing) · poetry licensing Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint poetic works as NFTs to facilitate transparent licensing and royalty tracking. Why Hedera: NFT provenance ensures clear ownership for automated royalty flows. Market: TAM $1.5B — writing tools market | SAM $130M — poetry licensing market | SOM $13M — NFT poetry licensors ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "PoetPrints" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint poetic works as NFTs to facilitate transparent licensing and royalty tracking. Discipline: Writing, Poetry & Narrative (poetry licensing). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance ensures clear ownership for automated royalty flows. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "PoetPrints" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-dialogue-dao-21 Title: Dialogue DAO Theme: Writing, Poetry & Narrative (writing) · scriptwriting collaboration Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint dialogue NFTs for decentralized collaboration with verified authorship. Why Hedera: HTS NFT tokens provide transparent proof for multi-writer dialogue ownership. Market: TAM $1.5B — writing tools market | SAM $160M — collaborative scriptwriting users | SOM $16M — NFT dialogue collaboration users ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Dialogue DAO" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint dialogue NFTs for decentralized collaboration with verified authorship. Discipline: Writing, Poetry & Narrative (scriptwriting collaboration). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide transparent proof for multi-writer dialogue ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Dialogue DAO" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-musemint-22 Title: MuseMint Theme: Writing, Poetry & Narrative (writing) · creative prompts Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint prompt NFTs to claim originality and track prompt provenance for writers. Why Hedera: NFTs ensure immutable proof of prompt creation and ownership. Market: TAM $1.5B — global writing tools market | SAM $90M — creative writing prompt platforms | SOM $9M — NFT prompt creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "MuseMint" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint prompt NFTs to claim originality and track prompt provenance for writers. Discipline: Writing, Poetry & Narrative (creative prompts). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFTs ensure immutable proof of prompt creation and ownership. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "MuseMint" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-fiction-flow-23 Title: Fiction Flow Theme: Writing, Poetry & Narrative (writing) · serialized fiction Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint serialized fiction episodes as NFTs to prove originality and ownership over time. Why Hedera: HTS NFT tokens provide provenance for sequential episode content. Market: TAM $1.5B — writing tools market | SAM $200M — serialized fiction digital tools | SOM $20M — NFT episodic fiction creators ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Fiction Flow" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint serialized fiction episodes as NFTs to prove originality and ownership over time. Discipline: Writing, Poetry & Narrative (serialized fiction). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: HTS NFT tokens provide provenance for sequential episode content. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Fiction Flow" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14 ------------------------------------------------------------------------------ IDEA writing-verse-vault-24 Title: Verse Vault Theme: Writing, Poetry & Narrative (writing) · spoken word poetry Hedera hook: HTS NFT provenance mint [onchain authorship] Pitch: Mint spoken word performances as NFTs to certify creator and preserve provenance. Why Hedera: NFT provenance anchors multimedia IPFS content with creator ownership proofs. Market: TAM $1.5B — global writing tools market | SAM $70M — spoken word poetry communities | SOM $7M — NFT spoken word artists ------------------------------------------------------------------------------ MEGAPROMPT (paste as a single Lovable message — it is self-contained): Build "Verse Vault" in ONE Lovable message. Single-page demo on Hedera testnet. CONCEPT Mint spoken word performances as NFTs to certify creator and preserve provenance. Discipline: Writing, Poetry & Narrative (spoken word poetry). Onchain primitive: native Hedera services — HCS for ordered, immutable records and HTS for value, with a Solidity contract only if the logic truly needs on-chain state. Why: NFT provenance anchors multimedia IPFS content with creator ownership proofs. 5-CREDIT BUDGET (HARD LIMIT) - ONE single-page app. No Lovable Cloud, no database, no auth beyond the Magic email OTP. - Default to NO smart contract deploy: HTS moves value, HCS records provenance (see below). - At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea). - Skip tests, skip CI, skip docs pages. Ship the demo. STACK - React + Vite + TanStack Start (the template Lovable ships). - Magic Link embedded wallet (email OTP) + viem over the Hedera JSON-RPC relay. - @hashgraph/sdk (WEB build) server-side for HCS/HTS with the operator account. - Confirmation always read from the mirror node, never from an RPC receipt. HEDERA TESTNET FACTS (copy these verbatim — do not invent endpoints) - CAIP-2 network id: hedera:testnet - EVM chain id: 296 (0x128) - JSON-RPC relay: https://testnet.hashio.io/api <- use the ABSOLUTE url, see rule 2 - Mirror node REST: https://testnet.mirrornode.hedera.com/api/v1 - Explorer: https://hashscan.io/testnet (tx: /transaction/, topic: /topic/) - HBAR faucet: https://portal.hedera.com/faucet (free, instant, needs a portal login) - Testnet USDC faucet: https://faucet.circle.com/ -> pick "Hedera Testnet" - Testnet USDC: token id 0.0.429274 · EVM address 0x0000000000000000000000000000000000068cda · 6 decimals - HBAR has 8 decimals (tinybars) on-chain, but the JSON-RPC relay speaks 18-decimal weibars. - Every account has TWO address shapes: the long-zero form (0x + hex account num, left-padded to 40) and the ECDSA `evm_address` alias. GET /accounts/{idOrAddress} returns both (`account`, `evm_address`). Put all of this in src/data/hedera.json and read it from there. Never inline a relay url, token id, or payee address inside a component. CONFIG FILE (src/data/hedera.json) { "networkName": "Hedera Testnet", "caip2": "hedera:testnet", "chainId": 296, "jsonRpcRelay": "https://testnet.hashio.io/api", "mirrorNode": "https://testnet.mirrornode.hedera.com/api/v1", "explorer": "https://hashscan.io/testnet", "usdcTokenId": "0.0.429274", "usdcEvmAddress": "0x0000000000000000000000000000000000068cda", "usdcDecimals": 6, "magicPublishableKey": "pk_live_...", "payTo": "", "hbarFaucetUrl": "https://portal.hedera.com/faucet", "usdcFaucetUrl": "https://faucet.circle.com/" } WALLET + EMAIL ONBOARDING (Magic Link embedded wallet — this is the path that works) - Use magic-sdk with the Hedera testnet relay as its custom EVM transport, plus viem for reads. The Magic PUBLISHABLE key (pk_live_...) is public: keep it in src/data/hedera.json, NOT in secrets. import { Magic } from "magic-sdk"; const magic = new Magic(hedera.magicPublishableKey, { network: { rpcUrl: "https://testnet.hashio.io/api", chainId: 296 }, }); await magic.auth.loginWithEmailOTP({ email }); // email OTP, no seed phrase const provider = magic.rpcProvider; // EIP-1193 - BEFORE writing any wallet code, open the Magic dashboard and configure it (this is the single biggest time sink — no code change can work around it): Magic Dashboard -> your app -> Settings -> "Allowed Origins & Redirects": add your Lovable preview origin AND your published origin. Same page -> "Content Security Policy" -> add https://testnet.hashio.io Any RPC host that is not allow-listed is blocked INSIDE the wallet iframe and surfaces as `Magic RPC Error: [-32603] Failed to fetch`. The app-side fetch to the same host still works, which makes this look like a code bug for hours. It is not. - Mount all wallet code CLIENT-ONLY: lazy(() => import("./magic-hedera-entry")) inside + . The SDK touches window and crashes SSR at module scope. - Rebuild the Magic instance AND the viem client whenever the rpc url changes, keyed on the url. A cached SDK instance keeps its original transport, so a corrected url never takes effect and you keep staring at the same stale "Failed to fetch". - Do NOT proxy the relay through your own /api route for the wallet. A same-origin path is unreachable from inside the Magic iframe even though it is perfect for app-side reads. - Read the address defensively: the SDK's user metadata can lag. Fall back to `provider.request({ method: "eth_accounts" })` before rendering "unknown address". - ONBOARDING UX (learned the hard way): do not block the demo behind a "provisioning wallet..." spinner. A brand-new Hedera account only exists once it is funded, and mirror-node indexing takes seconds. Sign the user in immediately, show a "needs funding" state, and let them explore. - While signed in, ALWAYS render (outside any conditional funding block): * copyable Account ID (0.0.x) AND copyable EVM address (0x...) * HBAR balance and USDC balance * both faucet links, with the note: the Circle faucet wants the 0.0.x ACCOUNT ID, not the EVM address — funding the EVM address silently leaves the USDC balance at 0 * a "Refresh balance" button — balances are polled, never pushed. NATIVE SERVICES (prefer these over Solidity) - HTS (token service) for anything that moves value: TransferTransaction, or an ERC-20 `transfer` through the relay against the token's EVM address. ~$0.001 per transfer, fixed. - HCS (consensus service) for append-only logs, provenance and timestamping: create ONE topic with the operator, then submit messages. ~$0.0001 per message, ordered and immutable. // src/lib/hedera-hcs.server.ts (web build + WebClient, see bundle gotchas) import { WebClient, TopicMessageSubmitTransaction, PrivateKey } from "@hashgraph/sdk/src/browser"; const client = WebClient.forTestnet().setOperator( process.env["HEDERA_OPERATOR_ID"]!, PrivateKey.fromStringECDSA(process.env["HEDERA_OPERATOR_KEY"]!)); const rx = await (await new TopicMessageSubmitTransaction({ topicId, message }) .execute(client)).getReceipt(client); Read history back from the mirror node: GET /topics/{topicId}/messages?order=desc&limit=25 (base64 `message` field). After submitting, POLL for the new message before re-rendering, and make each row clickable: https://hashscan.io/testnet/topic/{topicId}?mn={consensusTimestamp} - A Solidity contract is only justified when the logic itself is on-chain state that neither HTS nor HCS models. If you do deploy: chain id 296 through the relay, verify on HashScan (Sourcify-backed). There is no Etherscan and no ETHERSCAN_API_KEY on Hedera — do not add one. GAS (silent killer) An HBAR transfer through the JSON-RPC relay needs ~900_000 gas. At 120_000 it reverts on-chain with INSUFFICIENT_GAS while the client-side call REPORTS SUCCESS and prints a hash — only HashScan shows the truth. Set `gas: 900000n` on every relay-sent value transfer and contract call from the start. MIRROR-NODE VERIFICATION (server-side only, never trust the client) - The mirror node LAGS consensus by seconds. Never read once after submitting: poll with a bounded retry loop (8-10 attempts, ~1.5s apart). Treat "not found yet" as pending, never as failure. - HBAR / native transfer: GET /transactions/{id} -> result === "SUCCESS", check payee, payer and amount in tinybars (8 decimals; 0.01 HBAR = 1000000 tinybars). - EVM call (any transfer sent through the relay): GET /contracts/results/{hash} -> status "0x1". - HTS USDC moved by an ERC-20 `transfer` through the relay: verify from the Transfer LOG on that EVM result. Do NOT try to hop to a consensus record — /contracts/results/{hash} has no `transaction_id`, and the parent consensus record's `token_transfers` array is EMPTY (the HTS movement lands on a child synthetic CRYPTOTRANSFER you cannot reach from the EVM hash). The authoritative signal ships with the EVM record: logs[i].address = 0x0000000000000000000000000000000000068cda (USDC) topics[0] = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef (Transfer) topics[1] = payer (32-byte left-padded) -> `0x${topic.slice(-40)}` topics[2] = payee (32-byte left-padded) -> `0x${topic.slice(-40)}` data = amount, atomic units (6 decimals; 0x2710 = 10000 = 0.01 USDC) Sum BigInt(log.data) over matching logs and compare against the required amount. - Match addresses against BOTH shapes (long-zero and alias) for payer and payee. `tx.from` uses one shape while event topics use the other; comparing a single shape produces false rejections such as `declared payer ... is not the sender`. - HTS tokens must be ASSOCIATED with an account before it can receive them. Gate the USDC path on an association step (HIP-719 / `associate`) or the transfer fails with TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. - Return descriptive failure reasons (`invalid_payload: expected 10000 atomic units, saw 0`). The on-screen flow log is the only diagnostic your demo viewer has. BUILD / BUNDLE GOTCHAS - Hedera SDKs drag Node built-ins into the browser bundle. `No such module "node:process"` means you imported the Node build: use the SDK's web/browser entry and `WebClient`, and alias `pino` to its browser build in vite.config.ts: resolve: { alias: { pino: "pino/browser.js" } } - Server-only Hedera SDK work belongs in a *.server.ts module called from a server function; never import it from a component. - Read process.env INSIDE the handler of a server function, never at module scope. FILE LAYOUT src/data/hedera.json network + public config (above) src/lib/magic.ts Magic + viem clients, rebuilt when the rpc url changes src/lib/hedera-hcs.server.ts HCS topic submit (web build + WebClient) src/lib/hedera.functions.ts createServerFn wrappers the UI calls (thin: no module-scope helpers) src/components/magic-hedera-entry.tsx CLIENT-ONLY: email OTP, address rows, balances, faucet links src/routes/index.tsx the demo: sign in -> act -> HashScan receipt USER FLOW (render every step as a live flow log — it is your demo video) 1. Land on the page -> "Sign in with email" -> Magic OTP -> signed in immediately. 2. Wallet card shows copyable Account ID + EVM address, HBAR and USDC balances, both faucet links and a Refresh balance button. 3. User performs the core "Verse Vault" action; the app writes it to Hedera (HCS message or HTS transfer) through a server function and renders the HashScan receipt link. 4. History list reads back from the mirror node, POLLS for the new entry (do not make the user refresh the page), and every row is clickable through to HashScan. 5. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14" FAILURE MODES (symptom -> cause -> fix) | Symptom | Cause | Fix | |---|---|---| | `Magic RPC Error: [-32603] Failed to fetch` | RPC host missing from Magic CSP, or you used a same-origin proxy path | allow-list the host in the Magic dashboard; pin the absolute Hashio url | | corrected RPC url still fails | cached Magic instance kept the old transport | rebuild Magic + viem clients when the url changes | | client says success, HashScan says FAILED | 120k gas on a relay transfer | raise gas to ~900k | | `expected 10000 atomic units ... saw 0` | read the parent record's empty `token_transfers` | verify from the ERC-20 Transfer log | | `settlement_not_found: no consensus transaction yet` | tried to hop to a consensus record from an EVM hash | same as above | | `declared payer ... is not the sender` | compared only long-zero or only alias | match both forms from /accounts/{id} | | USDC balance stays 0 after the faucet | funded the EVM address, or token not associated | use the 0.0.x Account ID; associate first | | `No such module "node:process"` | Node build of a Hedera SDK in the browser bundle | web build + WebClient; alias pino to browser | | `settlement_not_found` right after submit | read the mirror node once | poll with bounded retries | | blank page / `window is not defined` | wallet code imported at module scope of a route | lazy() inside | WHAT WE'D DO DIFFERENTLY (do these first, in this order) 1. Allow-list https://testnet.hashio.io + your origins in the Magic dashboard CSP BEFORE writing a line of wallet code. 2. Set gas to 900_000 on the very first relay transfer instead of debugging a "successful" revert. 3. Verify HTS-over-EVM from the Transfer log from the start; never chase consensus token_transfers. 4. Poll the mirror node with retries everywhere; never read once and conclude. 5. Render Account ID, EVM address, both balances and both faucet links unconditionally while signed in — half the debugging was people not knowing which address to fund. 6. Reach for native services (HTS for tokens, HCS for append-only logs/timestamps) before Solidity. Deploy a contract only when the logic itself is on-chain state no native service models. REQUIRED SECRETS (Lovable -> Project Settings -> Secrets) - HEDERA_OPERATOR_ID Merchant/operator testnet account id (0.0.xxxxxx): https://portal.hedera.com/faucet - HEDERA_OPERATOR_KEY ECDSA private key for that account: https://portal.hedera.com/dashboard (server-only: HTS association, HCS topic create + submit. Never ship it to the browser.) NOT secrets (public config, put them in src/data/hedera.json): - the Magic PUBLISHABLE key (pk_live_...) from https://dashboard.magic.link - the relay url, mirror node url, explorer url, USDC token id / EVM address, and payTo account. CREDIT (must appear in the UI footer, and as NatSpec if you deploy any contract): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14